test: 优化 CodeStyle 测试流程

This commit is contained in:
Fu Diwei
2021-11-10 12:59:08 +08:00
parent 11ce0773a1
commit c8ee389674
20 changed files with 334 additions and 218 deletions

View File

@@ -2,19 +2,24 @@
<PropertyGroup>
<TargetFrameworks>netcoreapp3.1; net6.0</TargetFrameworks>
<LangVersion>8.0</LangVersion>
<LangVersion>9.0</LangVersion>
</PropertyGroup>
<ItemGroup>
<None Remove=".gitignore" />
<Content Include="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="appsettings.json">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</Content>
<Content Include="appsettings.*.json" Condition="'$(Configuration)' == 'Debug'">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Include="ModelSamples/**/*.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<PrivateAssets>all</PrivateAssets>

View File

@@ -8,20 +8,27 @@ namespace SKIT.FlurlHttpClient.Wechat.Ads.UnitTests
{
static TestConfigs()
{
// NOTICE: 请在项目根目录下按照 appsettings.json 的格式填入测试参数。
// NOTICE: 请在项目根目录下按照 appsettings.json 的格式新建 appsettings.local.json 填入测试参数。
// WARN: 敏感信息请不要提交到 git
using var stream = File.OpenRead("appsettings.json");
using var json = JsonDocument.Parse(stream);
try
{
using var stream = File.OpenRead("appsettings.local.json");
using var jdoc = JsonDocument.Parse(stream);
var config = json.RootElement.GetProperty("WechatConfig");
WechatAgencyId = config.GetProperty("AgencyId").GetString();
WechatAgencyAppId = config.GetProperty("AgencyAppId").GetString();
WechatAgencyApiKey = config.GetProperty("AgencyApiKey").GetString();
WechatAccessToken = config.GetProperty("AccessToken").GetString();
var config = jdoc.RootElement.GetProperty("WechatConfig");
WechatAgencyId = config.GetProperty("AgencyId").GetString();
WechatAgencyAppId = config.GetProperty("AgencyAppId").GetString();
WechatAgencyApiKey = config.GetProperty("AgencyApiKey").GetString();
WechatAccessToken = config.GetProperty("AccessToken").GetString();
ProjectSourceDirectory = json.RootElement.GetProperty("ProjectSourceDirectory").GetString();
ProjectTestDirectory = json.RootElement.GetProperty("ProjectTestDirectory").GetString();
ProjectSourceDirectory = jdoc.RootElement.GetProperty("ProjectSourceDirectory").GetString();
ProjectTestDirectory = jdoc.RootElement.GetProperty("ProjectTestDirectory").GetString();
}
catch (Exception ex)
{
throw new Exception("加载配置文件 appsettings.local.json 失败", ex);
}
}
public static readonly string WechatAgencyId;

View File

@@ -28,8 +28,6 @@ namespace SKIT.FlurlHttpClient.Wechat.Ads.UnitTests
public void ApiModelsDefinitionTest()
{
string workdir = Path.Combine(TestConfigs.ProjectTestDirectory, "ModelSamples");
Assert.True(Directory.Exists(workdir));
CodeStyleUtil.VerifyApiModelsDefinition(_assembly, workdir, out var ex);
if (ex != null)
@@ -53,8 +51,6 @@ namespace SKIT.FlurlHttpClient.Wechat.Ads.UnitTests
public void CodeStyleTest()
{
string workdir = Path.Combine(TestConfigs.ProjectSourceDirectory);
Assert.True(Directory.Exists(workdir));
CodeStyleUtil.VerifySourceCodeStyle(workdir, out var ex);
if (ex != null)

View File

@@ -5,6 +5,6 @@
"AgencyApiKey": "请在此填写用于测试的微信广告 AgencyApiKey",
"AccessToken": "请在此填写用于测试的微信广告 AccessToken"
},
"ProjectSourceDirectory": "请输入当前 SDK 项目所在的目录完整路径,如 C:\\Project\\src\\SKIT.FlurlHttpClient.Wechat.Api\\",
"ProjectTestDirectory": "请输入当前测试项目所在的目录完整路径,如 C:\\Project\\test\\SKIT.FlurlHttpClient.Wechat.Api.UnitTests\\"
"ProjectSourceDirectory": "请输入当前 SDK 项目所在的目录完整路径,如 C:\\Project\\src\\SKIT.FlurlHttpClient.Wechat.Ads\\",
"ProjectTestDirectory": "请输入当前测试项目所在的目录完整路径,如 C:\\Project\\test\\SKIT.FlurlHttpClient.Wechat.Ads.UnitTests\\"
}

View File

@@ -2,21 +2,26 @@
<PropertyGroup>
<TargetFrameworks>netcoreapp3.1; net6.0</TargetFrameworks>
<LangVersion>8.0</LangVersion>
<LangVersion>9.0</LangVersion>
</PropertyGroup>
<ItemGroup>
<None Remove=".gitignore" />
<Content Include="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="appsettings.json">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</Content>
<Content Include="appsettings.*.json" Condition="'$(Configuration)' == 'Debug'">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Include="ModelSamples/**/*.json" />
<Content Include="EventSamples/**/*.json" />
<Content Include="EventSamples/**/*.xml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<PrivateAssets>all</PrivateAssets>

View File

@@ -8,20 +8,27 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.UnitTests
{
static TestConfigs()
{
// NOTICE: 请在项目根目录下按照 appsettings.json 的格式填入测试参数。
// NOTICE: 请在项目根目录下按照 appsettings.json 的格式新建 appsettings.local.json 填入测试参数。
// WARN: 敏感信息请不要提交到 git
using var stream = File.OpenRead("appsettings.json");
using var json = JsonDocument.Parse(stream);
try
{
using var stream = File.OpenRead("appsettings.local.json");
using var jdoc = JsonDocument.Parse(stream);
var config = json.RootElement.GetProperty("WechatConfig");
WechatAppId = config.GetProperty("AppId").GetString();
WechatAppSecret = config.GetProperty("AppSecret").GetString();
WechatAccessToken = config.GetProperty("AccessToken").GetString();
WechatOpenId = config.GetProperty("OpenId").GetString();
var config = jdoc.RootElement.GetProperty("WechatConfig");
WechatAppId = config.GetProperty("AppId").GetString();
WechatAppSecret = config.GetProperty("AppSecret").GetString();
WechatAccessToken = config.GetProperty("AccessToken").GetString();
WechatOpenId = config.GetProperty("OpenId").GetString();
ProjectSourceDirectory = json.RootElement.GetProperty("ProjectSourceDirectory").GetString();
ProjectTestDirectory = json.RootElement.GetProperty("ProjectTestDirectory").GetString();
ProjectSourceDirectory = jdoc.RootElement.GetProperty("ProjectSourceDirectory").GetString();
ProjectTestDirectory = jdoc.RootElement.GetProperty("ProjectTestDirectory").GetString();
}
catch (Exception ex)
{
throw new Exception("加载配置文件 appsettings.local.json 失败", ex);
}
}
public static readonly string WechatAppId;

View File

@@ -28,8 +28,6 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.UnitTests
public void ApiModelsDefinitionTest()
{
string workdir = Path.Combine(TestConfigs.ProjectTestDirectory, "ModelSamples");
Assert.True(Directory.Exists(workdir));
CodeStyleUtil.VerifyApiModelsDefinition(_assembly, workdir, out var ex);
if (ex != null)
@@ -42,8 +40,6 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.UnitTests
public void ApiEventsDefinitionTest()
{
string workdir = Path.Combine(TestConfigs.ProjectTestDirectory, "EventSamples");
Assert.True(Directory.Exists(workdir));
CodeStyleUtil.VerifyApiEventsDefinition(_assembly, workdir, out var ex);
if (ex != null)
@@ -67,8 +63,6 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.UnitTests
public void CodeStyleTest()
{
string workdir = Path.Combine(TestConfigs.ProjectSourceDirectory);
Assert.True(Directory.Exists(workdir));
CodeStyleUtil.VerifySourceCodeStyle(workdir, out var ex);
if (ex != null)

View File

@@ -2,20 +2,25 @@
<PropertyGroup>
<TargetFrameworks>netcoreapp3.1; net6.0</TargetFrameworks>
<LangVersion>8.0</LangVersion>
<LangVersion>9.0</LangVersion>
</PropertyGroup>
<ItemGroup>
<None Remove=".gitignore" />
<Content Include="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="appsettings.json">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</Content>
<Content Include="appsettings.*.json" Condition="'$(Configuration)' == 'Debug'">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Include="ModelSamples/**/*.json" />
<Content Include="EventSamples/**/*.xml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<PrivateAssets>all</PrivateAssets>

View File

@@ -8,22 +8,29 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests
{
static TestConfigs()
{
// NOTICE: 请在项目根目录下按照 appsettings.json 的格式填入测试参数。
// NOTICE: 请在项目根目录下按照 appsettings.json 的格式新建 appsettings.local.json 填入测试参数。
// WARN: 敏感信息请不要提交到 git
using var stream = File.OpenRead("appsettings.json");
using var json = JsonDocument.Parse(stream);
try
{
using var stream = File.OpenRead("appsettings.local.json");
using var jdoc = JsonDocument.Parse(stream);
var config = json.RootElement.GetProperty("WechatConfig");
WechatClientId = config.GetProperty("ClientId").GetString();
WechatClientKey = config.GetProperty("ClientKey").GetString();
WechatAppId = config.GetProperty("AppId").GetString();
WechatToken = config.GetProperty("Token").GetString();
WechatEncodingAESKey = config.GetProperty("EncodingAESKey").GetString();
WechatAccessToken = config.GetProperty("AccessToken").GetString();
var config = jdoc.RootElement.GetProperty("WechatConfig");
WechatClientId = config.GetProperty("ClientId").GetString();
WechatClientKey = config.GetProperty("ClientKey").GetString();
WechatAppId = config.GetProperty("AppId").GetString();
WechatToken = config.GetProperty("Token").GetString();
WechatEncodingAESKey = config.GetProperty("EncodingAESKey").GetString();
WechatAccessToken = config.GetProperty("AccessToken").GetString();
ProjectSourceDirectory = json.RootElement.GetProperty("ProjectSourceDirectory").GetString();
ProjectTestDirectory = json.RootElement.GetProperty("ProjectTestDirectory").GetString();
ProjectSourceDirectory = jdoc.RootElement.GetProperty("ProjectSourceDirectory").GetString();
ProjectTestDirectory = jdoc.RootElement.GetProperty("ProjectTestDirectory").GetString();
}
catch (Exception ex)
{
throw new Exception("加载配置文件 appsettings.local.json 失败", ex);
}
}
public static readonly string WechatClientId;

View File

@@ -28,8 +28,6 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests
public void ApiModelsDefinitionTest()
{
string workdir = Path.Combine(TestConfigs.ProjectTestDirectory, "ModelSamples");
Assert.True(Directory.Exists(workdir));
CodeStyleUtil.VerifyApiModelsDefinition(_assembly, workdir, out var ex);
if (ex != null)
@@ -42,8 +40,6 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests
public void ApiEventsDefinitionTest()
{
string workdir = Path.Combine(TestConfigs.ProjectTestDirectory, "EventSamples");
Assert.True(Directory.Exists(workdir));
CodeStyleUtil.VerifyApiEventsDefinition(_assembly, workdir, out var ex);
if (ex != null)
@@ -67,8 +63,6 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests
public void CodeStyleTest()
{
string workdir = Path.Combine(TestConfigs.ProjectSourceDirectory);
Assert.True(Directory.Exists(workdir));
CodeStyleUtil.VerifySourceCodeStyle(workdir, out var ex);
if (ex != null)

View File

@@ -2,6 +2,9 @@
"WechatConfig": {
"ClientId": "请在此填写用于测试的微信智能对话 ClientId",
"ClientKey": "请在此填写用于测试的微信智能对话 ClientKey",
"AppId": "请在此填写用于测试的微信智能对话 AppId",
"Token": "请在此填写用于测试的微信智能对话 Token",
"EncodingAESKey": "请在此填写用于测试的微信智能对话 EncodingAESKey",
"AccessToken": "请在此填写用于测试的微信智能对话 AccessToken"
},
"ProjectSourceDirectory": "请输入当前 SDK 项目所在的目录完整路径,如 C:\\Project\\src\\SKIT.FlurlHttpClient.Wechat.OpenAI\\",

View File

@@ -1,21 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PropertyGroup>
<TargetFrameworks>netcoreapp3.1; net6.0</TargetFrameworks>
<LangVersion>8.0</LangVersion>
<LangVersion>9.0</LangVersion>
</PropertyGroup>
<ItemGroup>
<None Remove=".gitignore" />
<Content Include="appsettings.json">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</Content>
<Content Include="appsettings.*.json" Condition="'$(Configuration)' == 'Debug'">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Include="ModelSamples/**/*.json" />
<Content Include="EventSamples/**/*.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<PrivateAssets>all</PrivateAssets>

View File

@@ -8,22 +8,29 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
{
static TestConfigs()
{
// NOTICE: 请在项目根目录下按照 appsettings.json 的格式填入测试参数。
// NOTICE: 请在项目根目录下按照 appsettings.json 的格式新建 appsettings.local.json 填入测试参数。
// WARN: 敏感信息请不要提交到 git
using var stream = File.OpenRead("appsettings.json");
using var json = JsonDocument.Parse(stream);
try
{
using var stream = File.OpenRead("appsettings.local.json");
using var jdoc = JsonDocument.Parse(stream);
var config = json.RootElement.GetProperty("WechatConfig");
WechatAppId = config.GetProperty("AppId").GetString();
WechatMerchantId = config.GetProperty("MerchantId").GetString();
WechatMerchantSecret = config.GetProperty("MerchantSecret").GetString();
WechatMerchantCertSerialNumber = config.GetProperty("MerchantCertSerialNumber").GetString();
WechatMerchantCertPrivateKey = config.GetProperty("MerchantCertPrivateKey").GetString();
WechatOpenId = config.GetProperty("OpenId").GetString();
var config = jdoc.RootElement.GetProperty("WechatConfig");
WechatAppId = config.GetProperty("AppId").GetString();
WechatMerchantId = config.GetProperty("MerchantId").GetString();
WechatMerchantSecret = config.GetProperty("MerchantSecret").GetString();
WechatMerchantCertSerialNumber = config.GetProperty("MerchantCertSerialNumber").GetString();
WechatMerchantCertPrivateKey = config.GetProperty("MerchantCertPrivateKey").GetString();
WechatOpenId = config.GetProperty("OpenId").GetString();
ProjectSourceDirectory = json.RootElement.GetProperty("ProjectSourceDirectory").GetString();
ProjectTestDirectory = json.RootElement.GetProperty("ProjectTestDirectory").GetString();
ProjectSourceDirectory = jdoc.RootElement.GetProperty("ProjectSourceDirectory").GetString();
ProjectTestDirectory = jdoc.RootElement.GetProperty("ProjectTestDirectory").GetString();
}
catch (Exception ex)
{
throw new Exception("加载配置文件 appsettings.local.json 失败", ex);
}
}
public static readonly string WechatAppId;

View File

@@ -28,8 +28,6 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
public void ApiModelsDefinitionTest()
{
string workdir = Path.Combine(TestConfigs.ProjectTestDirectory, "ModelSamples");
Assert.True(Directory.Exists(workdir));
CodeStyleUtil.VerifyApiModelsDefinition(_assembly, workdir, out var ex);
if (ex != null)
@@ -42,8 +40,6 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
public void ApiEventsDefinitionTest()
{
string workdir = Path.Combine(TestConfigs.ProjectTestDirectory, "EventSamples");
Assert.True(Directory.Exists(workdir));
CodeStyleUtil.VerifyApiEventsDefinition(_assembly, workdir, out var ex);
if (ex != null)
@@ -67,8 +63,6 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
public void CodeStyleTest()
{
string workdir = Path.Combine(TestConfigs.ProjectSourceDirectory);
Assert.True(Directory.Exists(workdir));
CodeStyleUtil.VerifySourceCodeStyle(workdir, out var ex);
if (ex != null)

View File

@@ -11,7 +11,10 @@ namespace SKIT.FlurlHttpClient.Wechat
{
public static class CodeStyleUtil
{
private static bool TryJsonize(string json, Type type, out Exception exception)
private static readonly Regex CODESTYLE_RULE_REGEX_NO_JSONABLE_PROPERTY_IN_REQUEST = new Regex(@"\/\* \@codestyle-disable[a-zA-Z0-9,\-\s]*no-jsonable-property-in-request-get[a-zA-Z0-9,\-\s]*\*\/", RegexOptions.Compiled);
private static readonly Regex CODESTYLE_RULE_REGEX_NO_INSTANTIATED_PROPERTY_IN_RESPONSE = new Regex(@"\/\* \@codestyle-disable[a-zA-Z0-9,\-\s]*no-instantiated-property-in-response[a-zA-Z0-9,\-\s]*\*\/", RegexOptions.Compiled);
private static bool TryJsonize(Type type, string json, out Exception exception)
{
exception = null;
@@ -29,11 +32,13 @@ namespace SKIT.FlurlHttpClient.Wechat
catch (Exception ex)
{
if (ex is Newtonsoft.Json.JsonException)
exception = new Exception($"通过 Newtonsoft.Json 反序列化 `{type.Name}` 失败。", ex);
exception = new Exception($"[模型] 通过 Newtonsoft.Json 反序列化 `{type.Name}` 失败。", ex);
else if (ex is System.Text.Json.JsonException)
exception = new Exception($"通过 System.Text.Json 反序列化 `{type.Name}` 失败。", ex);
exception = new Exception($"[模型] 通过 System.Text.Json 反序列化 `{type.Name}` 失败。", ex);
else
exception = new Exception($"JSON 反序列化 `{type.Name}` 遇到问题。", ex);
exception = new Exception($"[模型] JSON 反序列化 `{type.Name}` 遇到问题。", ex);
return false;
}
try
@@ -47,20 +52,97 @@ namespace SKIT.FlurlHttpClient.Wechat
catch (Exception ex)
{
if (ex is Newtonsoft.Json.JsonException)
exception = new Exception($"通过 Newtonsoft.Json 序列化 `{type.Name}` 失败。", ex);
exception = new Exception($"[模型] 通过 Newtonsoft.Json 序列化 `{type.Name}` 失败。", ex);
else if (ex is System.Text.Json.JsonException)
exception = new Exception($"通过 System.Text.Json 序列化 `{type.Name}` 失败。", ex);
exception = new Exception($"[模型] 通过 System.Text.Json 序列化 `{type.Name}` 失败。", ex);
else
exception = new Exception($"JSON 序列化 `{type.Name}` 遇到问题。", ex);
exception = new Exception($"[模型] JSON 序列化 `{type.Name}` 遇到问题。", ex);
return false;
}
PropertyInfo[] lstPropInfo = TestReflectionUtil.GetAllProperties(type);
foreach (PropertyInfo propInfo in lstPropInfo)
try
{
var newtonsoftJsonAttribute = propInfo.GetCustomAttribute<Newtonsoft.Json.JsonPropertyAttribute>();
var systemTextJsonAttribute = propInfo.GetCustomAttribute<System.Text.Json.Serialization.JsonPropertyNameAttribute>();
if (newtonsoftJsonAttribute?.PropertyName != systemTextJsonAttribute?.Name)
exception = new Exception($"类型 `{type.Name}` 的可 JSON 序列化字段声明不一致:`{newtonsoftJsonAttribute.PropertyName}` & `{systemTextJsonAttribute.Name}`。");
PropertyInfo[] lstPropInfo = TestReflectionUtil.GetAllProperties(type);
foreach (PropertyInfo propInfo in lstPropInfo)
{
var newtonsoftJsonAttribute = propInfo.GetCustomAttribute<Newtonsoft.Json.JsonPropertyAttribute>(inherit: true);
var systemTextJsonAttribute = propInfo.GetCustomAttribute<System.Text.Json.Serialization.JsonPropertyNameAttribute>(inherit: true);
if (newtonsoftJsonAttribute?.PropertyName != systemTextJsonAttribute?.Name)
{
exception = new Exception($"[模型] 类型 `{type.Name}` 的可 JSON 序列化字段 `{propInfo.Name}` 声明不一致:`{newtonsoftJsonAttribute.PropertyName}` & `{systemTextJsonAttribute.Name}`。");
return false;
}
}
}
catch (Exception ex)
{
exception = new Exception($"[模型] JSON 分析 `{type.Name}` 遇到问题。", ex);
return false;
}
return exception == null;
}
private static bool TryXmlize(Type type, string xml, out Exception exception)
{
exception = null;
try
{
using var reader = new StringReader(xml);
var xmlSerializer = new XmlSerializer(type, new XmlRootAttribute("xml"));
xmlSerializer.Deserialize(reader);
}
catch (Exception ex)
{
exception = new Exception($"[模型] XML 反序列化 `{type.Name}` 遇到问题。", ex);
return false;
}
try
{
var xmlDoc = new System.Xml.XmlDocument();
xmlDoc.LoadXml(xml);
Func<System.Xml.XmlNode, Exception> fn = default;
fn = new Func<System.Xml.XmlNode, Exception>((xmlNode) =>
{
if (xmlNode.NodeType == System.Xml.XmlNodeType.Element)
{
PropertyInfo[] lstPropInfo = TestReflectionUtil.GetAllProperties(type);
var xmlElementAttributes = lstPropInfo.SelectMany(e => e.GetCustomAttributes<XmlElementAttribute>(inherit: true)).ToArray();
var xmlArrayAttributes = lstPropInfo.SelectMany(e => e.GetCustomAttributes<XmlArrayAttribute>(inherit: true)).ToArray();
var xmlArrayItemAttributes = lstPropInfo.SelectMany(e => e.GetCustomAttributes<XmlArrayItemAttribute>(inherit: true)).ToArray();
if (!xmlElementAttributes.Any(e => e.ElementName == xmlNode.Name) &&
!xmlArrayAttributes.Any(e => e.ElementName == xmlNode.Name) &&
!xmlArrayItemAttributes.Any(e => e.ElementName == xmlNode.Name))
{
return new Exception($"[模型] 类型 `{type.Name}` 的可 XML 序列化字段缺失项:`{xmlNode.Name}`。");
}
}
for (int i = 0; i < xmlNode.ChildNodes.Count; i++)
{
Exception ex = fn.Invoke(xmlNode.ChildNodes[i]);
if (ex != null)
return ex;
}
return null;
});
for (int i = 0; i < xmlDoc.DocumentElement.ChildNodes.Count; i++)
{
exception = fn.Invoke(xmlDoc.DocumentElement.ChildNodes[i]);
if (exception != null)
return false;
}
}
catch (Exception ex)
{
exception = new Exception($"[模型] XML 分析 `{type.Name}` 遇到问题。", ex);
return false;
}
return exception == null;
@@ -79,7 +161,7 @@ namespace SKIT.FlurlHttpClient.Wechat
if (!name.EndsWith("Request") && !name.EndsWith("Response"))
{
lstError.Add(new Exception($"`{name}` 类名结尾应为 \"Request\" 或 \"Response\"。"));
lstError.Add(new Exception($"[模型] `{name}` 类名结尾应为 \"Request\" 或 \"Response\"。"));
continue;
}
@@ -87,13 +169,13 @@ namespace SKIT.FlurlHttpClient.Wechat
{
if (!typeof(ICommonRequest).IsAssignableFrom(modelType))
{
lstError.Add(new Exception($"`{name}` 类需实现自 `ICommonRequest`。"));
lstError.Add(new Exception($"[模型] `{name}` 是请求模型,需实现自 `{nameof(ICommonRequest)}`。"));
continue;
}
if (!lstModelType.Any(e => e.Name == $"{name.Substring(0, name.Length - "Request".Length)}Response"))
{
lstError.Add(new Exception($"`{name}` 是请求模型,但不存在对应的响应模型。"));
lstError.Add(new Exception($"[模型] `{name}` 是请求模型,但不存在对应的响应模型。"));
continue;
}
}
@@ -102,13 +184,13 @@ namespace SKIT.FlurlHttpClient.Wechat
{
if (!typeof(ICommonResponse).IsAssignableFrom(modelType))
{
lstError.Add(new Exception($"`{name}` 类需实现自 `ICommonResponse`。"));
lstError.Add(new Exception($"[模型] `{name}` 是响应模型,需实现自 `{nameof(ICommonResponse)}`。"));
continue;
}
if (!lstModelType.Any(e => e.Name == $"{name.Substring(0, name.Length - "Response".Length)}Request"))
{
lstError.Add(new Exception($"`{name}` 是响应模型,但不存在对应的请求模型。"));
lstError.Add(new Exception($"[模型] `{name}` 是响应模型,但不存在对应的请求模型。"));
continue;
}
}
@@ -132,29 +214,32 @@ namespace SKIT.FlurlHttpClient.Wechat
var lstModelType = TestReflectionUtil.GetAllApiModelsTypes(assembly);
var lstError = new List<Exception>();
var lstFile = TestIOUtil.GetAllFiles(workdir)
var lstFilePath = TestIOUtil.GetAllFiles(workdir)
.Where(e => string.Equals(Path.GetExtension(e), ".json", StringComparison.InvariantCultureIgnoreCase))
.ToList();
if (!lstFile.Any())
if (!lstFilePath.Any())
{
lstError.Add(new Exception($"路径 \"{workdir}\" 下不存在 JSON 格式的模型示例文件,请检查路径是否正确。"));
lstError.Add(new Exception($"[模型] \"{workdir}\" 下不存在示例文件,请检查配置的扫描路径是否正确。"));
}
foreach (string file in lstFile)
foreach (string filePath in lstFilePath)
{
string json = File.ReadAllText(file);
string name = Path.GetFileNameWithoutExtension(file).Split('.')[0];
string name = Path.GetFileNameWithoutExtension(filePath).Split('.')[0];
Type type = assembly.GetTypes().SingleOrDefault(e => e.FullName.StartsWith($"{assembly.GetName().Name}.Models.") && e.Name == name);
Type type = assembly.GetTypes().FirstOrDefault(e => e.FullName.StartsWith($"{assembly.GetName().Name}.Models.") && e.Name == name);
if (type == null)
{
lstError.Add(new Exception($"类型 `{name}`不存在。"));
lstError.Add(new Exception($"[模型] 扫描到示例文件 \"{filePath}\",但类型 `{name}` 不存在。"));
continue;
}
if (!TryJsonize(json, type, out Exception ex))
if (string.Equals(Path.GetExtension(filePath), ".json", StringComparison.InvariantCultureIgnoreCase))
{
lstError.Add(ex);
string json = File.ReadAllText(filePath);
if (!TryJsonize(type, json, out Exception ex))
{
lstError.Add(ex);
}
}
}
@@ -176,56 +261,45 @@ namespace SKIT.FlurlHttpClient.Wechat
var lstModelType = TestReflectionUtil.GetAllApiModelsTypes(assembly);
var lstError = new List<Exception>();
var lstJsonFile = TestIOUtil.GetAllFiles(workdir)
.Where(e => string.Equals(Path.GetExtension(e), ".json", StringComparison.InvariantCultureIgnoreCase))
var lstFilePath = TestIOUtil.GetAllFiles(workdir)
.Where(e =>
string.Equals(Path.GetExtension(e), ".json", StringComparison.InvariantCultureIgnoreCase) ||
string.Equals(Path.GetExtension(e), ".xml", StringComparison.InvariantCultureIgnoreCase)
)
.ToArray();
var lstXmlFile = TestIOUtil.GetAllFiles(workdir)
.Where(e => string.Equals(Path.GetExtension(e), ".xml", StringComparison.InvariantCultureIgnoreCase))
.ToArray();
if (!lstJsonFile.Any() && !lstXmlFile.Any())
if (!lstFilePath.Any())
{
lstError.Add(new Exception($"路径 \"{workdir}\" 下不存在 JSON 或 XML 格式的事件示例文件,请检查路径是否正确。"));
lstError.Add(new Exception($"[模型] \"{workdir}\" 下不存在示例文件,请检查配置的扫描路径是否正确。"));
}
foreach (string file in lstJsonFile)
foreach (string filePath in lstFilePath)
{
string json = File.ReadAllText(file);
string name = Path.GetFileNameWithoutExtension(file).Split('.')[0];
string name = Path.GetFileNameWithoutExtension(filePath).Split('.')[0];
Type type = assembly.GetTypes().SingleOrDefault(e => e.FullName.StartsWith($"{assembly.GetName().Name}.Events.") && e.Name == name);
if (type == null)
{
lstError.Add(new Exception($"类型 `{name}`不存在。"));
lstError.Add(new Exception($"[模型] 扫描到示例文件 \"{filePath}\",但类型 `{name}` 不存在。"));
continue;
}
if (!TryJsonize(json, type, out Exception ex))
if (string.Equals(Path.GetExtension(filePath), ".json", StringComparison.InvariantCultureIgnoreCase))
{
lstError.Add(ex);
}
}
string json = File.ReadAllText(filePath);
foreach (string file in lstXmlFile)
{
string xml = File.ReadAllText(file);
string name = Path.GetFileNameWithoutExtension(file).Split('.')[0];
Type type = assembly.GetTypes().SingleOrDefault(e => e.FullName.StartsWith($"{assembly.GetName().Name}.Events.") && e.Name == name);
if (type == null)
{
lstError.Add(new Exception($"类型 `{name}`不存在。"));
continue;
if (!TryJsonize(type, json, out Exception ex))
{
lstError.Add(ex);
}
}
else if (string.Equals(Path.GetExtension(filePath), ".xml", StringComparison.InvariantCultureIgnoreCase))
{
string xml = File.ReadAllText(filePath);
try
{
using StringReader reader = new StringReader(xml);
XmlSerializer xmlSerializer = new XmlSerializer(type, new XmlRootAttribute("xml"));
xmlSerializer.Deserialize(reader);
}
catch (Exception ex)
{
lstError.Add(new Exception($"XML 反序列化 `{type.Name}` 遇到问题。", ex));
if (!TryXmlize(type, xml, out Exception ex))
{
lstError.Add(ex);
}
}
}
@@ -246,12 +320,11 @@ namespace SKIT.FlurlHttpClient.Wechat
var lstExtType = TestReflectionUtil.GetAllApiExtensionsTypes(assembly);
var lstError = new List<Exception>();
foreach (Type extType in lstExtType)
foreach (Type extensionsType in lstExtType)
{
MethodInfo[] lstMethod = extType.GetMethods()
MethodInfo[] lstMethod = extensionsType.GetMethods()
.Where(e =>
e.IsPublic &&
e.IsStatic &&
e.IsPublic && e.IsStatic &&
typeof(ICommonClient).IsAssignableFrom(e.GetParameters().FirstOrDefault().ParameterType)
)
.ToArray();
@@ -260,17 +333,15 @@ namespace SKIT.FlurlHttpClient.Wechat
{
ParameterInfo[] lstParamInfo = methodInfo.GetParameters();
// 参数签名必为 this client + request + cancelToken
if (lstParamInfo.Length != 3)
{
lstError.Add(new Exception($"`{extType.Name}.{methodInfo.Name}` 方法需有且仅有 3 个入参。"));
lstError.Add(new Exception($"[接口] `{extensionsType.Name}.{methodInfo.Name}` 方法需有且仅有 3 个入参。"));
continue;
}
// 第二个参数必为 ICommonRequest 子类
if (!typeof(ICommonRequest).IsAssignableFrom(lstParamInfo[1].ParameterType))
{
lstError.Add(new Exception($"`{extType.Name}.{methodInfo.Name}` 方法第 1 个入参实现 `ICommonRequest`。"));
lstError.Add(new Exception($"[接口] `{extensionsType.Name}.{methodInfo.Name}` 方法第 1 个入参实现 `{nameof(ICommonRequest)}`。"));
continue;
}
@@ -280,25 +351,25 @@ namespace SKIT.FlurlHttpClient.Wechat
string retv = methodInfo.ReturnType.GenericTypeArguments.FirstOrDefault()?.Name;
if (para == null || !para.EndsWith("Request"))
{
lstError.Add(new Exception($"`{extType.Name}.{methodInfo.Name}` 方法第 1 个入参类名以 `Request` 结尾。"));
lstError.Add(new Exception($"[接口] `{extensionsType.Name}.{methodInfo.Name}` 方法第 1 个入参类名以 `Request` 结尾。"));
continue;
}
else if (retv == null || !retv.EndsWith("Response"))
{
if (!methodInfo.ReturnType.GenericTypeArguments.First().IsGenericType)
{
lstError.Add(new Exception($"`{extType.Name}.{methodInfo.Name}` 方法返回值类名以 `Response` 结尾。"));
lstError.Add(new Exception($"[接口] `{extensionsType.Name}.{methodInfo.Name}` 方法返回值类名以 `Response` 结尾。"));
}
continue;
}
else if (!string.Equals(func, $"Execute{para.Substring(0, para.Length - "Request".Length)}Async"))
{
lstError.Add(new Exception($"`{extType.Name}.{methodInfo.Name}` 方法与请求模型同名。"));
lstError.Add(new Exception($"[接口] `{extensionsType.Name}.{methodInfo.Name}` 方法与请求模型同名。"));
continue;
}
else if (!string.Equals(func, $"Execute{retv.Substring(0, retv.Length - "Response".Length)}Async"))
{
lstError.Add(new Exception($"`{extType.Name}.{methodInfo.Name}` 方法与响应模型同名。"));
lstError.Add(new Exception($"[接口] `{extensionsType.Name}.{methodInfo.Name}` 方法与响应模型同名。"));
continue;
}
}
@@ -323,7 +394,6 @@ namespace SKIT.FlurlHttpClient.Wechat
var lstExtensionsCodeFile = TestIOUtil.GetAllFiles(workdir)
.Where(e => string.Equals(Path.GetExtension(e), ".cs", StringComparison.InvariantCultureIgnoreCase))
.Where(e => Path.GetDirectoryName(e).StartsWith(Path.Combine(workdir, "Extensions")))
.Where(e => Path.GetFileNameWithoutExtension(e).StartsWith("Wechat"))
.Where(e => Path.GetFileNameWithoutExtension(e).Contains("ClientExecute"))
.Where(e => Path.GetFileNameWithoutExtension(e).EndsWith("Extensions"))
.ToArray();
@@ -334,18 +404,18 @@ namespace SKIT.FlurlHttpClient.Wechat
.ToArray();
if (!lstExtensionsCodeFile.Any() || !lstModelsCodeFile.Any())
{
lstError.Add(new Exception($"路径 \"{workdir}\" 下不存在 CSharp 格式的源代码文件,请检查路径是否正确。"));
lstError.Add(new Exception($"[风格] \"{workdir}\" 下不存在源代码文件,请检查配置的扫描路径是否正确。"));
}
foreach (string extCodeFilePath in lstExtensionsCodeFile)
{
string extCodeFileName = Path.GetFileName(extCodeFilePath);
string[] array = File.ReadAllText(extCodeFilePath)
string[] segments = File.ReadAllText(extCodeFilePath)
.Split("<summary>", StringSplitOptions.RemoveEmptyEntries)
.Where(e => e.Contains("Async") && !e.Contains("public static class"))
.ToArray();
for (int i = 0; i < array.Length; i++)
for (int i = 0; i < segments.Length; i++)
{
bool TryCheckExtensionSourceCode(string sourceCode, out (string Name, string Method, string Url)? request)
{
@@ -355,7 +425,7 @@ namespace SKIT.FlurlHttpClient.Wechat
var regexPara = new Regex("<para(([\\s\\S])*?)</para>").Match(sourceCode);
if (!regexPara.Success)
{
lstError.Add(new Exception($"源代码 \"{extCodeFileName}\" 下第 {i + 1} 段文档注释不齐全,未能匹配到 \"<para> ... </para>\"。"));
lstError.Add(new Exception($"[风格] 源代码 \"{extCodeFileName}\" 下第 {i + 1} 段文档注释不齐全,未能匹配到 \"<para> ... </para>\"。"));
return false;
}
@@ -363,7 +433,7 @@ namespace SKIT.FlurlHttpClient.Wechat
var regexApi = new Regex("\\[(\\S*)\\]\\s*(\\S*)").Match(regexPara.Groups[1].Value);
if (!regexApi.Success)
{
lstError.Add(new Exception($"源代码 \"{extCodeFileName}\" 下第 {i + 1} 段文档注释不齐全,未能匹配到 \"异步调用 ... 接口\"。"));
lstError.Add(new Exception($"[风格] 源代码 \"{extCodeFileName}\" 下第 {i + 1} 段文档注释不齐全,未能匹配到 \"异步调用 ... 接口\"。"));
return false;
}
@@ -377,7 +447,7 @@ namespace SKIT.FlurlHttpClient.Wechat
string.Empty;
if (!string.Equals(expectedMethod, actualMethod, StringComparison.OrdinalIgnoreCase))
{
lstError.Add(new Exception($"源代码 \"{extCodeFileName}\" 下第 {i + 1} 段文档注释有误,`[{expectedMethod}] {expectedUrl}` 与实际接口谓词不一致。"));
lstError.Add(new Exception($"[风格] 源代码 \"{extCodeFileName}\" 下第 {i + 1} 段文档注释有误,`[{expectedMethod}] {expectedUrl}` 与实际接口谓词不一致。"));
return false;
}
@@ -393,7 +463,7 @@ namespace SKIT.FlurlHttpClient.Wechat
string[] actualUrlSegments = actualUrl.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(e => e.Trim()).ToArray();
if (expectedUrlSegments.Length != actualUrlSegments.Length)
{
lstError.Add(new Exception($"源代码 \"{extCodeFileName}\" 下第 {i + 1} 段文档注释有误,`[{expectedMethod}] {expectedUrl}` 与实际接口路由不一致。"));
lstError.Add(new Exception($"[风格] 源代码 \"{extCodeFileName}\" 下第 {i + 1} 段文档注释有误,`[{expectedMethod}] {expectedUrl}` 与实际接口路由不一致。"));
return false;
}
else
@@ -406,7 +476,7 @@ namespace SKIT.FlurlHttpClient.Wechat
{
if (actualUrlSegment.StartsWith("\""))
{
lstError.Add(new Exception($"源代码 \"{extCodeFileName}\" 下第 {i + 1} 段文档注释有误,`[{expectedMethod}] {expectedUrl}` 与实际接口路由不一致。"));
lstError.Add(new Exception($"[风格] 源代码 \"{extCodeFileName}\" 下第 {i + 1} 段文档注释有误,`[{expectedMethod}] {expectedUrl}` 与实际接口路由不一致。"));
break;
}
}
@@ -415,7 +485,7 @@ namespace SKIT.FlurlHttpClient.Wechat
actualUrlSegment = actualUrlSegment.Replace("\"", string.Empty).Trim('/');
if (!string.Equals(expectedUrlSegment, actualUrlSegment))
{
lstError.Add(new Exception($"源代码 \"{extCodeFileName}\" 下第 {i + 1} 段文档注释有误,`[{expectedMethod}] {expectedUrl}` 与实际接口路由不一致。"));
lstError.Add(new Exception($"[风格] 源代码 \"{extCodeFileName}\" 下第 {i + 1} 段文档注释有误,`[{expectedMethod}] {expectedUrl}` 与实际接口路由不一致。"));
break;
}
}
@@ -427,7 +497,7 @@ namespace SKIT.FlurlHttpClient.Wechat
{
if (sourceCode.Contains("flurlReq, cancellationToken"))
{
lstError.Add(new Exception($"源代码 \"{extCodeFileName}\" 下第 {i + 1} 段代码有误,`[{expectedMethod}] {expectedUrl}` 为非简单请求但不包含请求正文。"));
lstError.Add(new Exception($"[风格] 源代码 \"{extCodeFileName}\" 下第 {i + 1} 段代码有误,`[{expectedMethod}] {expectedUrl}` 为非简单请求但不包含请求正文。"));
return false;
}
}
@@ -438,6 +508,12 @@ namespace SKIT.FlurlHttpClient.Wechat
}
bool TryCheckRequestModelSourceCode(string filePath, string expectedRequestMethod, string expectedRequestUrl)
{
if (!File.Exists(filePath))
{
lstError.Add(new Exception($"[风格] 源代码 \"{filePath}\" 不存在。"));
return false;
}
string reqCodeFileName = Path.GetFileName(filePath);
string reqCodeSourceCode = File.ReadAllText(filePath);
@@ -445,7 +521,7 @@ namespace SKIT.FlurlHttpClient.Wechat
var regexPara = new Regex("<para(([\\s\\S])*?)</para>").Match(reqCodeSourceCode);
if (!regexPara.Success)
{
lstError.Add(new Exception($"源代码 \"{reqCodeFileName}\" 下文档注释不齐全,未能匹配到 \"<para> ... </para>\"。"));
lstError.Add(new Exception($"[风格] 源代码 \"{reqCodeFileName}\" 下文档注释不齐全,未能匹配到 \"<para> ... </para>\"。"));
return false;
}
@@ -453,7 +529,7 @@ namespace SKIT.FlurlHttpClient.Wechat
var regexApi = new Regex("\\[(\\S*)\\]\\s*(\\S*)").Match(regexPara.Groups[1].Value);
if (!regexApi.Success)
{
lstError.Add(new Exception($"源代码 \"{reqCodeFileName}\" 下文档注释不齐全,未能匹配到 \"异步调用 ... 接口\"。"));
lstError.Add(new Exception($"[风格] 源代码 \"{reqCodeFileName}\" 下文档注释不齐全,未能匹配到 \"异步调用 ... 接口\"。"));
return false;
}
@@ -461,7 +537,7 @@ namespace SKIT.FlurlHttpClient.Wechat
string actualMethod = regexApi.Groups[1].Value.Trim();
if (!string.Equals(expectedRequestMethod, actualMethod, StringComparison.OrdinalIgnoreCase))
{
lstError.Add(new Exception($"源代码 \"{reqCodeFileName}\" 下文档注释有误,与实际接口谓词不一致。"));
lstError.Add(new Exception($"[风格] 源代码 \"{reqCodeFileName}\" 下文档注释有误,与实际接口谓词不一致。"));
return false;
}
@@ -469,45 +545,45 @@ namespace SKIT.FlurlHttpClient.Wechat
string actualUrl = regexApi.Groups[2].Value.Split('?')[0].Trim();
if (!string.Equals(expectedRequestUrl, actualUrl, StringComparison.OrdinalIgnoreCase))
{
lstError.Add(new Exception($"源代码 \"{reqCodeFileName}\" 下文档注释有误,与实际接口路由不一致。"));
lstError.Add(new Exception($"[风格] 源代码 \"{reqCodeFileName}\" 下文档注释有误,与实际接口路由不一致。"));
return false;
}
// 检验是否包含 `default!` 的赋值
if (new Regex("=\\s*default!").IsMatch(reqCodeSourceCode))
{
lstError.Add(new Exception($"源代码 \"{reqCodeFileName}\" 下代码有误,请求模型不应包含 `= default!` 赋值。"));
lstError.Add(new Exception($"[风格] 源代码 \"{reqCodeFileName}\" 下代码有误,请求模型不应包含 `= default!` 赋值。"));
return false;
}
// 检验是否包含数组类型字段
if (new Regex("public\\s*[a-zA-Z0-9.]*\\[\\]\\s*[a-zA-Z0-9]*\\s*{\\s*get;\\s*set;\\s*}").IsMatch(reqCodeSourceCode.Replace("byte[]", string.Empty).Replace("Byte[]", string.Empty)))
{
lstError.Add(new Exception($"源代码 \"{reqCodeFileName}\" 下代码有误,请求模型不应包含数组类型字段。"));
lstError.Add(new Exception($"[风格] 源代码 \"{reqCodeFileName}\" 下代码有误,请求模型不应包含数组类型字段。"));
return false;
}
// 校验是否包含可空字段的初始化
if (new Regex("\\? [a-zA-Z0-9]* \\{ get; set; \\} =").IsMatch(reqCodeSourceCode))
{
lstError.Add(new Exception($"源代码 \"{reqCodeFileName}\" 下代码有误,可空字段不应初始化。"));
lstError.Add(new Exception($"[风格] 源代码 \"{reqCodeFileName}\" 下代码有误,可空字段不应初始化。"));
return false;
}
// 如果是 GET 请求,检查是否包含 JSON 序列化字段
if ("GET".Equals(expectedRequestMethod, StringComparison.OrdinalIgnoreCase))
{
if (!(reqCodeSourceCode.Contains("/* @codestyle-disable") && reqCodeSourceCode.Contains("no-jsonable-property-in-request-get")))
if (!(CODESTYLE_RULE_REGEX_NO_JSONABLE_PROPERTY_IN_REQUEST.IsMatch(reqCodeSourceCode)))
{
if (new Regex("\\[Newtonsoft.Json.JsonProperty\\(\"[a-zA-Z0-9_]*\"\\)\\]").IsMatch(reqCodeSourceCode))
{
lstError.Add(new Exception($"源代码 \"{reqCodeFileName}\" 下代码有误,请求模型为简单请求、不应包含可 JSON 序列化字段。"));
lstError.Add(new Exception($"[风格] 源代码 \"{reqCodeFileName}\" 下代码有误,请求模型为简单请求、不应包含可 JSON 序列化字段。"));
return false;
}
if (new Regex("\\[System.Text.Json.Serialization.JsonPropertyName\\(\"[a-zA-Z0-9_]*\"\\)\\]").IsMatch(reqCodeSourceCode))
{
lstError.Add(new Exception($"源代码 \"{reqCodeFileName}\" 下代码有误,请求模型为简单请求、不应包含可 JSON 序列化字段。"));
lstError.Add(new Exception($"[风格] 源代码 \"{reqCodeFileName}\" 下代码有误,请求模型为简单请求、不应包含可 JSON 序列化字段。"));
return false;
}
}
@@ -517,6 +593,12 @@ namespace SKIT.FlurlHttpClient.Wechat
}
bool TryCheckResponseModelSourceCode(string filePath, string expectedRequestMethod, string expectedRequestUrl)
{
if (!File.Exists(filePath))
{
lstError.Add(new Exception($"[风格] 源代码 \"{filePath}\" 不存在。"));
return false;
}
string resCodeFileName = Path.GetFileName(filePath);
string resCodeSourceCode = File.ReadAllText(filePath);
@@ -524,7 +606,7 @@ namespace SKIT.FlurlHttpClient.Wechat
var regexPara = new Regex("<para(([\\s\\S])*?)</para>").Match(resCodeSourceCode);
if (!regexPara.Success)
{
lstError.Add(new Exception($"源代码 \"{resCodeFileName}\" 下文档注释不齐全,未能匹配到 \"<para> ... </para>\"。"));
lstError.Add(new Exception($"[风格] 源代码 \"{resCodeFileName}\" 下文档注释不齐全,未能匹配到 \"<para> ... </para>\"。"));
return false;
}
@@ -532,7 +614,7 @@ namespace SKIT.FlurlHttpClient.Wechat
var regexApi = new Regex("\\[(\\S*)\\]\\s*(\\S*)").Match(regexPara.Groups[1].Value);
if (!regexApi.Success)
{
lstError.Add(new Exception($"源代码 \"{resCodeFileName}\" 下文档注释不齐全,未能匹配到 \"异步调用 ... 接口\"。"));
lstError.Add(new Exception($"[风格] 源代码 \"{resCodeFileName}\" 下文档注释不齐全,未能匹配到 \"异步调用 ... 接口\"。"));
return false;
}
@@ -540,7 +622,7 @@ namespace SKIT.FlurlHttpClient.Wechat
string actualMethod = regexApi.Groups[1].Value.Trim();
if (!string.Equals(expectedRequestMethod, actualMethod, StringComparison.OrdinalIgnoreCase))
{
lstError.Add(new Exception($"源代码 \"{resCodeFileName}\" 下文档注释有误,与实际接口谓词不一致。"));
lstError.Add(new Exception($"[风格] 源代码 \"{resCodeFileName}\" 下文档注释有误,与实际接口谓词不一致。"));
return false;
}
@@ -548,23 +630,23 @@ namespace SKIT.FlurlHttpClient.Wechat
string actualUrl = regexApi.Groups[2].Value.Split('?')[0].Trim();
if (!string.Equals(expectedRequestUrl, actualUrl, StringComparison.OrdinalIgnoreCase))
{
lstError.Add(new Exception($"源代码 \"{resCodeFileName}\" 下文档注释有误,与实际接口路由不一致。"));
lstError.Add(new Exception($"[风格] 源代码 \"{resCodeFileName}\" 下文档注释有误,与实际接口路由不一致。"));
return false;
}
// 检验是否包含 `string.Empty` 的赋值
if (new Regex("=\\s*string.Empty").IsMatch(resCodeSourceCode))
{
lstError.Add(new Exception($"源代码 \"{resCodeFileName}\" 下代码有误,响应模型不应包含 `= string.Empty` 赋值。"));
lstError.Add(new Exception($"[风格] 源代码 \"{resCodeFileName}\" 下代码有误,响应模型不应包含 `= string.Empty` 赋值。"));
return false;
}
// 检验是否包含 `new class()` 的赋值
if (!(resCodeSourceCode.Contains("/* @codestyle-disable") && resCodeSourceCode.Contains("no-instantiated-property-in-response")))
if (new Regex("=\\s*new\\s[a-zA-Z0-9.]*\\(\\)").IsMatch(resCodeSourceCode))
{
if (new Regex("=\\s*new\\s[a-zA-Z0-9.]*\\(\\)").IsMatch(resCodeSourceCode))
if (!(CODESTYLE_RULE_REGEX_NO_INSTANTIATED_PROPERTY_IN_RESPONSE.IsMatch(resCodeSourceCode)))
{
lstError.Add(new Exception($"源代码 \"{resCodeFileName}\" 下代码有误,响应模型不应包含 `= new class()` 赋值。"));
lstError.Add(new Exception($"[风格] 源代码 \"{resCodeFileName}\" 下代码有误,响应模型不应包含 `= new class()` 赋值。"));
return false;
}
}
@@ -572,23 +654,23 @@ namespace SKIT.FlurlHttpClient.Wechat
// 检验是否包含列表类型字段
if (new Regex("public\\s*IList<[a-zA-Z0-9.]*>\\s*[a-zA-Z0-9]*\\s*{\\s*get;\\s*set;\\s*}").IsMatch(resCodeSourceCode))
{
lstError.Add(new Exception($"源代码 \"{resCodeFileName}\" 下代码有误,响应模型不应包含列表类型字段。"));
lstError.Add(new Exception($"[风格] 源代码 \"{resCodeFileName}\" 下代码有误,响应模型不应包含列表类型字段。"));
return false;
}
return true;
}
if (!TryCheckExtensionSourceCode(array[i], out var request))
if (!TryCheckExtensionSourceCode(segments[i], out var request))
{
continue;
}
else
{
string requestModelFileName = lstModelsCodeFile.Single(e => string.Equals(Path.GetFileNameWithoutExtension(e), $"{request.Value.Name}Request"));
string responseModelFileName = lstModelsCodeFile.Single(e => string.Equals(Path.GetFileNameWithoutExtension(e), $"{request.Value.Name}Response"));
bool isValidReq = TryCheckRequestModelSourceCode(requestModelFileName, request.Value.Method, request.Value.Url);
bool isValidRes = TryCheckResponseModelSourceCode(responseModelFileName, request.Value.Method, request.Value.Url);
string reqModelFileName = lstModelsCodeFile.FirstOrDefault(e => string.Equals(Path.GetFileNameWithoutExtension(e), $"{request.Value.Name}Request"));
string resModelFileName = lstModelsCodeFile.FirstOrDefault(e => string.Equals(Path.GetFileNameWithoutExtension(e), $"{request.Value.Name}Response"));
bool isValidReq = TryCheckRequestModelSourceCode(reqModelFileName, request.Value.Method, request.Value.Url);
bool isValidRes = TryCheckResponseModelSourceCode(resModelFileName, request.Value.Method, request.Value.Url);
if (!isValidReq || !isValidRes)
{
continue;

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFrameworks>netcoreapp3.1; net6.0</TargetFrameworks>
<LangVersion>8.0</LangVersion>
<LangVersion>9.0</LangVersion>
</PropertyGroup>
<ItemGroup>

View File

@@ -2,21 +2,26 @@
<PropertyGroup>
<TargetFrameworks>netcoreapp3.1; net6.0</TargetFrameworks>
<LangVersion>8.0</LangVersion>
<LangVersion>9.0</LangVersion>
</PropertyGroup>
<ItemGroup>
<None Remove=".gitignore" />
<Content Include="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="appsettings.json">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</Content>
<Content Include="appsettings.*.json" Condition="'$(Configuration)' == 'Debug'">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Include="ModelSamples/**/*.json" />
<Content Include="EventSamples/**/*.json" />
<Content Include="EventSamples/**/*.xml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<PrivateAssets>all</PrivateAssets>

View File

@@ -8,19 +8,26 @@ namespace SKIT.FlurlHttpClient.Wechat.Work.UnitTests
{
static TestConfigs()
{
// NOTICE: 请在项目根目录下按照 appsettings.json 的格式填入测试参数。
// NOTICE: 请在项目根目录下按照 appsettings.json 的格式新建 appsettings.local.json 填入测试参数。
// WARN: 敏感信息请不要提交到 git
using var stream = File.OpenRead("appsettings.json");
using var json = JsonDocument.Parse(stream);
try
{
using var stream = File.OpenRead("appsettings.local.json");
using var jdoc = JsonDocument.Parse(stream);
var config = json.RootElement.GetProperty("WechatConfig");
WechatCorpId = config.GetProperty("CorpId").GetString();
WechatAgentId = int.Parse(config.GetProperty("AgentId").GetString());
WechatAgentSecret = config.GetProperty("AgentSecret").GetString();
var config = jdoc.RootElement.GetProperty("WechatConfig");
WechatCorpId = config.GetProperty("CorpId").GetString();
WechatAgentId = int.Parse(config.GetProperty("AgentId").GetString());
WechatAgentSecret = config.GetProperty("AgentSecret").GetString();
ProjectSourceDirectory = json.RootElement.GetProperty("ProjectSourceDirectory").GetString();
ProjectTestDirectory = json.RootElement.GetProperty("ProjectTestDirectory").GetString();
ProjectSourceDirectory = jdoc.RootElement.GetProperty("ProjectSourceDirectory").GetString();
ProjectTestDirectory = jdoc.RootElement.GetProperty("ProjectTestDirectory").GetString();
}
catch (Exception ex)
{
throw new Exception("加载配置文件 appsettings.local.json 失败", ex);
}
}
public static readonly string WechatCorpId;

View File

@@ -17,7 +17,6 @@ namespace SKIT.FlurlHttpClient.Wechat.Work.UnitTests
public void ApiModelsNamingTest()
{
CodeStyleUtil.VerifyApiModelsNaming(_assembly, out var ex);
if (ex != null)
throw ex;
@@ -28,8 +27,6 @@ namespace SKIT.FlurlHttpClient.Wechat.Work.UnitTests
public void ApiModelsDefinitionTest()
{
string workdir = Path.Combine(TestConfigs.ProjectTestDirectory, "ModelSamples");
Assert.True(Directory.Exists(workdir));
CodeStyleUtil.VerifyApiModelsDefinition(_assembly, workdir, out var ex);
if (ex != null)
@@ -42,8 +39,6 @@ namespace SKIT.FlurlHttpClient.Wechat.Work.UnitTests
public void ApiEventsDefinitionTest()
{
string workdir = Path.Combine(TestConfigs.ProjectTestDirectory, "EventSamples");
Assert.True(Directory.Exists(workdir));
CodeStyleUtil.VerifyApiEventsDefinition(_assembly, workdir, out var ex);
if (ex != null)
@@ -67,8 +62,6 @@ namespace SKIT.FlurlHttpClient.Wechat.Work.UnitTests
public void CodeStyleTest()
{
string workdir = Path.Combine(TestConfigs.ProjectSourceDirectory);
Assert.True(Directory.Exists(workdir));
CodeStyleUtil.VerifySourceCodeStyle(workdir, out var ex);
if (ex != null)

View File

@@ -5,5 +5,5 @@
"AgentSecret": "请在此填写用于测试的企业微信 AgentSecret"
},
"ProjectSourceDirectory": "请输入当前 SDK 项目所在的目录完整路径,如 C:\\Project\\src\\SKIT.FlurlHttpClient.Wechat.Work\\",
"ProjectTestDirectory": "请输入当前测试项目所在的目录完整路径,如 C:\\Project\\test\\SKIT.FlurlHttpClient.Wechat.TenpayV3.Work\\"
"ProjectTestDirectory": "请输入当前测试项目所在的目录完整路径,如 C:\\Project\\test\\SKIT.FlurlHttpClient.Wechat.Work.UnitTests\\"
}