test(config): 添加YAML配置加载器dependentSchemas约束功能测试

- 实现YAML配置加载器对对象级dependentSchemas约束的运行时行为验证
- 添加dependentSchemas不满足时抛出异常的测试用例
- 添加触发字段缺席时不误触发dependentSchemas检查的测试用例
- 添加dependentSchemas满足时正常加载并保留额外字段的测试用例
- 添加非对象类型dependentSchemas声明时抛出异常的测试用例
- 添加触发字段未在同级properties中声明时的错误处理测试
- 添加dependentSchemas条件子schema非object类型时的验证测试
- 实现临时目录管理以避免不同测试场景间的数据污染
- 提供完整的测试辅助方法包括文件创建、schema构建等功能
This commit is contained in:
GeWuYou 2026-04-17 11:47:06 +08:00
parent 2486352341
commit cde234ddea

View File

@ -10,6 +10,26 @@ namespace GFramework.Game.Tests.Config;
[TestFixture] [TestFixture]
public sealed class YamlConfigLoaderDependentSchemasTests public sealed class YamlConfigLoaderDependentSchemasTests
{ {
private const string DefaultRewardPropertiesJson = """
{
"itemId": { "type": "string" },
"itemCount": { "type": "integer" },
"bonus": { "type": "integer" }
}
""";
private const string DefaultDependentSchemasJson = """
{
"itemId": {
"type": "object",
"required": ["itemCount"],
"properties": {
"itemCount": { "type": "integer" }
}
}
}
""";
private string? _rootPath; private string? _rootPath;
/// <summary> /// <summary>
@ -43,39 +63,13 @@ public sealed class YamlConfigLoaderDependentSchemasTests
{ {
CreateConfigFile( CreateConfigFile(
"monster/slime.yaml", "monster/slime.yaml",
""" BuildMonsterConfigYaml(
id: 1 """
reward: itemId: potion
itemId: potion """));
""");
CreateSchemaFile( CreateSchemaFile(
"schemas/monster.schema.json", "schemas/monster.schema.json",
""" BuildMonsterSchema(DefaultRewardPropertiesJson, DefaultDependentSchemasJson));
{
"type": "object",
"required": ["id", "reward"],
"properties": {
"id": { "type": "integer" },
"reward": {
"type": "object",
"properties": {
"itemId": { "type": "string" },
"itemCount": { "type": "integer" },
"bonus": { "type": "integer" }
},
"dependentSchemas": {
"itemId": {
"type": "object",
"required": ["itemCount"],
"properties": {
"itemCount": { "type": "integer" }
}
}
}
}
}
}
""");
var loader = CreateMonsterRewardLoader(); var loader = CreateMonsterRewardLoader();
var registry = CreateRegistry(); var registry = CreateRegistry();
@ -101,39 +95,13 @@ public sealed class YamlConfigLoaderDependentSchemasTests
{ {
CreateConfigFile( CreateConfigFile(
"monster/slime.yaml", "monster/slime.yaml",
""" BuildMonsterConfigYaml(
id: 1 """
reward: bonus: 2
bonus: 2 """));
""");
CreateSchemaFile( CreateSchemaFile(
"schemas/monster.schema.json", "schemas/monster.schema.json",
""" BuildMonsterSchema(DefaultRewardPropertiesJson, DefaultDependentSchemasJson));
{
"type": "object",
"required": ["id", "reward"],
"properties": {
"id": { "type": "integer" },
"reward": {
"type": "object",
"properties": {
"itemId": { "type": "string" },
"itemCount": { "type": "integer" },
"bonus": { "type": "integer" }
},
"dependentSchemas": {
"itemId": {
"type": "object",
"required": ["itemCount"],
"properties": {
"itemCount": { "type": "integer" }
}
}
}
}
}
}
""");
var loader = CreateMonsterRewardLoader(); var loader = CreateMonsterRewardLoader();
var registry = CreateRegistry(); var registry = CreateRegistry();
@ -152,41 +120,15 @@ public sealed class YamlConfigLoaderDependentSchemasTests
{ {
CreateConfigFile( CreateConfigFile(
"monster/slime.yaml", "monster/slime.yaml",
""" BuildMonsterConfigYaml(
id: 1 """
reward: itemId: potion
itemId: potion itemCount: 3
itemCount: 3 bonus: 1
bonus: 1 """));
""");
CreateSchemaFile( CreateSchemaFile(
"schemas/monster.schema.json", "schemas/monster.schema.json",
""" BuildMonsterSchema(DefaultRewardPropertiesJson, DefaultDependentSchemasJson));
{
"type": "object",
"required": ["id", "reward"],
"properties": {
"id": { "type": "integer" },
"reward": {
"type": "object",
"properties": {
"itemId": { "type": "string" },
"itemCount": { "type": "integer" },
"bonus": { "type": "integer" }
},
"dependentSchemas": {
"itemId": {
"type": "object",
"required": ["itemCount"],
"properties": {
"itemCount": { "type": "integer" }
}
}
}
}
}
}
""");
var loader = CreateMonsterRewardLoader(); var loader = CreateMonsterRewardLoader();
var registry = CreateRegistry(); var registry = CreateRegistry();
@ -212,30 +154,22 @@ public sealed class YamlConfigLoaderDependentSchemasTests
{ {
CreateConfigFile( CreateConfigFile(
"monster/slime.yaml", "monster/slime.yaml",
""" BuildMonsterConfigYaml(
id: 1 """
reward: itemId: potion
itemId: potion """));
""");
CreateSchemaFile( CreateSchemaFile(
"schemas/monster.schema.json", "schemas/monster.schema.json",
""" BuildMonsterSchema(
{ """
"type": "object", {
"required": ["id", "reward"], "itemId": { "type": "string" },
"properties": { "itemCount": { "type": "integer" }
"id": { "type": "integer" },
"reward": {
"type": "object",
"properties": {
"itemId": { "type": "string" },
"itemCount": { "type": "integer" }
},
"dependentSchemas": ["itemId"]
} }
} """,
} """
"""); ["itemId"]
"""));
var loader = CreateMonsterRewardLoader(); var loader = CreateMonsterRewardLoader();
var registry = CreateRegistry(); var registry = CreateRegistry();
@ -260,36 +194,28 @@ public sealed class YamlConfigLoaderDependentSchemasTests
{ {
CreateConfigFile( CreateConfigFile(
"monster/slime.yaml", "monster/slime.yaml",
""" BuildMonsterConfigYaml(
id: 1 """
reward: itemId: potion
itemId: potion """));
""");
CreateSchemaFile( CreateSchemaFile(
"schemas/monster.schema.json", "schemas/monster.schema.json",
""" BuildMonsterSchema(
{ """
"type": "object", {
"required": ["id", "reward"], "itemCount": { "type": "integer" }
"properties": { }
"id": { "type": "integer" }, """,
"reward": { """
"type": "object", {
"properties": { "itemId": {
"itemCount": { "type": "integer" } "type": "object",
}, "properties": {
"dependentSchemas": { "itemCount": { "type": "integer" }
"itemId": {
"type": "object",
"properties": {
"itemCount": { "type": "integer" }
}
} }
} }
} }
} """));
}
""");
var loader = CreateMonsterRewardLoader(); var loader = CreateMonsterRewardLoader();
var registry = CreateRegistry(); var registry = CreateRegistry();
@ -314,34 +240,26 @@ public sealed class YamlConfigLoaderDependentSchemasTests
{ {
CreateConfigFile( CreateConfigFile(
"monster/slime.yaml", "monster/slime.yaml",
""" BuildMonsterConfigYaml(
id: 1 """
reward: itemId: potion
itemId: potion """));
""");
CreateSchemaFile( CreateSchemaFile(
"schemas/monster.schema.json", "schemas/monster.schema.json",
""" BuildMonsterSchema(
{ """
"type": "object", {
"required": ["id", "reward"], "itemId": { "type": "string" }
"properties": { }
"id": { "type": "integer" }, """,
"reward": { """
"type": "object", {
"properties": { "itemId": {
"itemId": { "type": "string" } "type": "string",
}, "const": "potion"
"dependentSchemas": {
"itemId": {
"type": "string",
"const": "potion"
}
} }
} }
} """));
}
""");
var loader = CreateMonsterRewardLoader(); var loader = CreateMonsterRewardLoader();
var registry = CreateRegistry(); var registry = CreateRegistry();
@ -387,6 +305,48 @@ public sealed class YamlConfigLoaderDependentSchemasTests
CreateConfigFile(relativePath, content); CreateConfigFile(relativePath, content);
} }
private static string BuildMonsterConfigYaml(string rewardYaml)
{
return $$"""
id: 1
reward:
{{IndentLines(rewardYaml, 2)}}
""";
}
private static string BuildMonsterSchema(
string rewardPropertiesJson,
string dependentSchemasJson)
{
return $$"""
{
"type": "object",
"required": ["id", "reward"],
"properties": {
"id": { "type": "integer" },
"reward": {
"type": "object",
"properties": {{rewardPropertiesJson}},
"dependentSchemas": {{dependentSchemasJson}}
}
}
}
""";
}
private static string IndentLines(string text, int indentLevel)
{
var indentation = new string(' ', indentLevel);
var lines = text
.Trim()
.Split('\n', StringSplitOptions.None)
.Select(static line => line.TrimEnd('\r'));
return string.Join(
Environment.NewLine,
lines.Select(line => $"{indentation}{line}"));
}
/// <summary> /// <summary>
/// 创建用于对象 dependentSchemas 场景的加载器。 /// 创建用于对象 dependentSchemas 场景的加载器。
/// </summary> /// </summary>