mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-07 00:39:00 +08:00
test(config): 添加配置验证功能的全面单元测试
- 实现了对嵌套对象和数组元数据解析的测试 - 添加了常量值比较和对象排序一致性的验证测试 - 创建了对YAML解析器嵌套映射和复杂键名的支持测试 - 开发了针对缺失和未知属性的验证诊断测试 - 实现了对象数组项目验证和深度枚举匹配测试 - 添加了标量、对象、数组、整数和布尔型常量验证测试 - 创建了数字范围和字符串长度限制验证测试 - 实现了独占边界、模式匹配和数组项目计数验证 - 添加了支持的字符串格式验证(日期、时间、邮箱等) - 创建了多重约束和唯一性检查验证测试 - 实现了包含匹配计数约束验证测试 - 添加了科学记数法和大数精度处理验证测试 - 创建了Unicode模式匹配和唯一项重复检测测试
This commit is contained in:
parent
0f5b3a98bf
commit
ebc53510ab
376
GFramework.Game.Tests/Config/YamlConfigLoaderNegationTests.cs
Normal file
376
GFramework.Game.Tests/Config/YamlConfigLoaderNegationTests.cs
Normal file
@ -0,0 +1,376 @@
|
|||||||
|
using System.IO;
|
||||||
|
using GFramework.Game.Abstractions.Config;
|
||||||
|
using GFramework.Game.Config;
|
||||||
|
|
||||||
|
namespace GFramework.Game.Tests.Config;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证 YAML 配置加载器对 <c>not</c> 约束的运行时行为。
|
||||||
|
/// </summary>
|
||||||
|
[TestFixture]
|
||||||
|
public sealed class YamlConfigLoaderNegationTests
|
||||||
|
{
|
||||||
|
private string _rootPath = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 为每个测试创建隔离的临时目录,避免不同 <c>not</c> 用例互相污染。
|
||||||
|
/// </summary>
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp()
|
||||||
|
{
|
||||||
|
_rootPath = Path.Combine(Path.GetTempPath(), "GFramework.ConfigTests", Guid.NewGuid().ToString("N"));
|
||||||
|
Directory.CreateDirectory(_rootPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 清理当前测试创建的临时目录,避免本地文件残留影响后续执行。
|
||||||
|
/// </summary>
|
||||||
|
[TearDown]
|
||||||
|
public void TearDown()
|
||||||
|
{
|
||||||
|
if (Directory.Exists(_rootPath))
|
||||||
|
{
|
||||||
|
Directory.Delete(_rootPath, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证运行时会拒绝命中 <c>not</c> 子 schema 的标量值。
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void LoadAsync_Should_Throw_When_Value_Matches_Not_Schema()
|
||||||
|
{
|
||||||
|
CreateConfigFile(
|
||||||
|
"monster/slime.yaml",
|
||||||
|
"""
|
||||||
|
id: 1
|
||||||
|
name: Deprecated
|
||||||
|
hp: 10
|
||||||
|
""");
|
||||||
|
CreateSchemaFile(
|
||||||
|
"schemas/monster.schema.json",
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["id", "name", "hp"],
|
||||||
|
"properties": {
|
||||||
|
"id": { "type": "integer" },
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"not": {
|
||||||
|
"type": "string",
|
||||||
|
"const": "Deprecated"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hp": { "type": "integer" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
var loader = new YamlConfigLoader(_rootPath)
|
||||||
|
.RegisterTable<int, MonsterConfigStub>("monster", "monster", "schemas/monster.schema.json",
|
||||||
|
static config => config.Id);
|
||||||
|
var registry = new ConfigRegistry();
|
||||||
|
|
||||||
|
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(exception, Is.Not.Null);
|
||||||
|
Assert.That(exception!.Diagnostic.FailureKind, Is.EqualTo(ConfigLoadFailureKind.ConstraintViolation));
|
||||||
|
Assert.That(exception.Diagnostic.DisplayPath, Is.EqualTo("name"));
|
||||||
|
Assert.That(exception.Message, Does.Contain("must not match the 'not' schema"));
|
||||||
|
Assert.That(registry.Count, Is.EqualTo(0));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证值未命中 <c>not</c> 子 schema 时,加载器不会误报禁用约束。
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public async Task LoadAsync_Should_Accept_When_Value_Does_Not_Match_Not_Schema()
|
||||||
|
{
|
||||||
|
CreateConfigFile(
|
||||||
|
"monster/slime.yaml",
|
||||||
|
"""
|
||||||
|
id: 1
|
||||||
|
name: Slime
|
||||||
|
hp: 10
|
||||||
|
""");
|
||||||
|
CreateSchemaFile(
|
||||||
|
"schemas/monster.schema.json",
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["id", "name", "hp"],
|
||||||
|
"properties": {
|
||||||
|
"id": { "type": "integer" },
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"not": {
|
||||||
|
"type": "string",
|
||||||
|
"const": "Deprecated"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hp": { "type": "integer" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
var loader = new YamlConfigLoader(_rootPath)
|
||||||
|
.RegisterTable<int, MonsterConfigStub>("monster", "monster", "schemas/monster.schema.json",
|
||||||
|
static config => config.Id);
|
||||||
|
var registry = new ConfigRegistry();
|
||||||
|
|
||||||
|
await loader.LoadAsync(registry);
|
||||||
|
|
||||||
|
var table = registry.GetTable<int, MonsterConfigStub>("monster");
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(table.Count, Is.EqualTo(1));
|
||||||
|
Assert.That(table.Get(1).Name, Is.EqualTo("Slime"));
|
||||||
|
Assert.That(table.Get(1).Hp, Is.EqualTo(10));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证对象完整命中禁用 schema 时,同样会触发 <c>not</c> 约束失败。
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void LoadAsync_Should_Throw_When_Object_Fully_Matches_Not_Schema()
|
||||||
|
{
|
||||||
|
CreateConfigFile(
|
||||||
|
"monster/slime.yaml",
|
||||||
|
"""
|
||||||
|
id: 1
|
||||||
|
reward:
|
||||||
|
gold: 10
|
||||||
|
""");
|
||||||
|
CreateSchemaFile(
|
||||||
|
"schemas/monster.schema.json",
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["id", "reward"],
|
||||||
|
"properties": {
|
||||||
|
"id": { "type": "integer" },
|
||||||
|
"reward": {
|
||||||
|
"type": "object",
|
||||||
|
"not": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["gold"],
|
||||||
|
"properties": {
|
||||||
|
"gold": { "type": "integer" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"gold": { "type": "integer" },
|
||||||
|
"bonus": { "type": "integer" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
var loader = new YamlConfigLoader(_rootPath)
|
||||||
|
.RegisterTable<int, MonsterRewardConfigStub>("monster", "monster", "schemas/monster.schema.json",
|
||||||
|
static config => config.Id);
|
||||||
|
var registry = new ConfigRegistry();
|
||||||
|
|
||||||
|
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(exception, Is.Not.Null);
|
||||||
|
Assert.That(exception!.Diagnostic.FailureKind, Is.EqualTo(ConfigLoadFailureKind.ConstraintViolation));
|
||||||
|
Assert.That(exception.Diagnostic.DisplayPath, Is.EqualTo("reward"));
|
||||||
|
Assert.That(exception.Message, Does.Contain("must not match the 'not' schema"));
|
||||||
|
Assert.That(registry.Count, Is.EqualTo(0));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证对象仅命中 <c>not</c> schema 的属性子集时,不会被误判为完整命中。
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public async Task LoadAsync_Should_Accept_When_Object_Does_Not_Strictly_Match_Not_Schema()
|
||||||
|
{
|
||||||
|
CreateConfigFile(
|
||||||
|
"monster/slime.yaml",
|
||||||
|
"""
|
||||||
|
id: 1
|
||||||
|
reward:
|
||||||
|
gold: 10
|
||||||
|
bonus: 5
|
||||||
|
""");
|
||||||
|
CreateSchemaFile(
|
||||||
|
"schemas/monster.schema.json",
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["id", "reward"],
|
||||||
|
"properties": {
|
||||||
|
"id": { "type": "integer" },
|
||||||
|
"reward": {
|
||||||
|
"type": "object",
|
||||||
|
"not": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["gold"],
|
||||||
|
"properties": {
|
||||||
|
"gold": { "type": "integer" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"gold": { "type": "integer" },
|
||||||
|
"bonus": { "type": "integer" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
var loader = new YamlConfigLoader(_rootPath)
|
||||||
|
.RegisterTable<int, MonsterRewardConfigStub>("monster", "monster", "schemas/monster.schema.json",
|
||||||
|
static config => config.Id);
|
||||||
|
var registry = new ConfigRegistry();
|
||||||
|
|
||||||
|
await loader.LoadAsync(registry);
|
||||||
|
|
||||||
|
var table = registry.GetTable<int, MonsterRewardConfigStub>("monster");
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(table.Count, Is.EqualTo(1));
|
||||||
|
Assert.That(table.Get(1).Reward.Gold, Is.EqualTo(10));
|
||||||
|
Assert.That(table.Get(1).Reward.Bonus, Is.EqualTo(5));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证 schema 将 <c>not</c> 声明为非对象值时,会在解析阶段被拒绝。
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void LoadAsync_Should_Throw_When_Not_Is_Not_An_Object()
|
||||||
|
{
|
||||||
|
CreateConfigFile(
|
||||||
|
"monster/slime.yaml",
|
||||||
|
"""
|
||||||
|
id: 1
|
||||||
|
name: Slime
|
||||||
|
hp: 10
|
||||||
|
""");
|
||||||
|
CreateSchemaFile(
|
||||||
|
"schemas/monster.schema.json",
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["id", "name", "hp"],
|
||||||
|
"properties": {
|
||||||
|
"id": { "type": "integer" },
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"not": "deprecated"
|
||||||
|
},
|
||||||
|
"hp": { "type": "integer" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
var loader = new YamlConfigLoader(_rootPath)
|
||||||
|
.RegisterTable<int, MonsterConfigStub>("monster", "monster", "schemas/monster.schema.json",
|
||||||
|
static config => config.Id);
|
||||||
|
var registry = new ConfigRegistry();
|
||||||
|
|
||||||
|
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(exception, Is.Not.Null);
|
||||||
|
Assert.That(exception!.Diagnostic.FailureKind, Is.EqualTo(ConfigLoadFailureKind.SchemaUnsupported));
|
||||||
|
Assert.That(exception.Diagnostic.DisplayPath, Is.EqualTo("name"));
|
||||||
|
Assert.That(exception.Message, Does.Contain("must declare 'not' as an object-valued schema"));
|
||||||
|
Assert.That(registry.Count, Is.EqualTo(0));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 在测试目录下写入配置文件,并自动创建缺失目录。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="relativePath">相对根目录的配置文件路径。</param>
|
||||||
|
/// <param name="content">要写入的 YAML 或 schema 内容。</param>
|
||||||
|
private void CreateConfigFile(string relativePath, string content)
|
||||||
|
{
|
||||||
|
var filePath = Path.Combine(_rootPath, relativePath.Replace('/', Path.DirectorySeparatorChar));
|
||||||
|
var directoryPath = Path.GetDirectoryName(filePath);
|
||||||
|
if (!string.IsNullOrEmpty(directoryPath))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(directoryPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
File.WriteAllText(filePath, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 写入测试 schema 文件,复用通用配置文件创建逻辑。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="relativePath">schema 相对路径。</param>
|
||||||
|
/// <param name="content">schema JSON 内容。</param>
|
||||||
|
private void CreateSchemaFile(string relativePath, string content)
|
||||||
|
{
|
||||||
|
CreateConfigFile(relativePath, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用于标量 <c>not</c> 回归测试的最小配置类型。
|
||||||
|
/// </summary>
|
||||||
|
private sealed class MonsterConfigStub
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置主键。
|
||||||
|
/// </summary>
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置名称。
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置生命值。
|
||||||
|
/// </summary>
|
||||||
|
public int Hp { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用于对象 <c>not</c> 回归测试的最小配置类型。
|
||||||
|
/// </summary>
|
||||||
|
private sealed class MonsterRewardConfigStub
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置主键。
|
||||||
|
/// </summary>
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置奖励对象。
|
||||||
|
/// </summary>
|
||||||
|
public RewardConfigStub Reward { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 表示对象 <c>not</c> 回归测试中的奖励节点。
|
||||||
|
/// </summary>
|
||||||
|
private sealed class RewardConfigStub
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置金币数量。
|
||||||
|
/// </summary>
|
||||||
|
public int Gold { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置额外奖励数量。
|
||||||
|
/// </summary>
|
||||||
|
public int Bonus { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1085,103 +1085,6 @@ public class YamlConfigLoaderTests
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 验证运行时会拒绝命中 <c>not</c> 子 schema 的标量值。
|
|
||||||
/// </summary>
|
|
||||||
[Test]
|
|
||||||
public void LoadAsync_Should_Throw_When_Value_Matches_Not_Schema()
|
|
||||||
{
|
|
||||||
CreateConfigFile(
|
|
||||||
"monster/slime.yaml",
|
|
||||||
"""
|
|
||||||
id: 1
|
|
||||||
name: Deprecated
|
|
||||||
hp: 10
|
|
||||||
""");
|
|
||||||
CreateSchemaFile(
|
|
||||||
"schemas/monster.schema.json",
|
|
||||||
"""
|
|
||||||
{
|
|
||||||
"type": "object",
|
|
||||||
"required": ["id", "name", "hp"],
|
|
||||||
"properties": {
|
|
||||||
"id": { "type": "integer" },
|
|
||||||
"name": {
|
|
||||||
"type": "string",
|
|
||||||
"not": {
|
|
||||||
"type": "string",
|
|
||||||
"const": "Deprecated"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"hp": { "type": "integer" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
""");
|
|
||||||
|
|
||||||
var loader = new YamlConfigLoader(_rootPath)
|
|
||||||
.RegisterTable<int, MonsterConfigStub>("monster", "monster", "schemas/monster.schema.json",
|
|
||||||
static config => config.Id);
|
|
||||||
var registry = new ConfigRegistry();
|
|
||||||
|
|
||||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
|
||||||
{
|
|
||||||
Assert.That(exception, Is.Not.Null);
|
|
||||||
Assert.That(exception!.Diagnostic.FailureKind, Is.EqualTo(ConfigLoadFailureKind.ConstraintViolation));
|
|
||||||
Assert.That(exception.Diagnostic.DisplayPath, Is.EqualTo("name"));
|
|
||||||
Assert.That(exception.Message, Does.Contain("must not match the 'not' schema"));
|
|
||||||
Assert.That(registry.Count, Is.EqualTo(0));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 验证 schema 将 <c>not</c> 声明为非对象值时,会在解析阶段被拒绝。
|
|
||||||
/// </summary>
|
|
||||||
[Test]
|
|
||||||
public void LoadAsync_Should_Throw_When_Not_Is_Not_An_Object()
|
|
||||||
{
|
|
||||||
CreateConfigFile(
|
|
||||||
"monster/slime.yaml",
|
|
||||||
"""
|
|
||||||
id: 1
|
|
||||||
name: Slime
|
|
||||||
hp: 10
|
|
||||||
""");
|
|
||||||
CreateSchemaFile(
|
|
||||||
"schemas/monster.schema.json",
|
|
||||||
"""
|
|
||||||
{
|
|
||||||
"type": "object",
|
|
||||||
"required": ["id", "name", "hp"],
|
|
||||||
"properties": {
|
|
||||||
"id": { "type": "integer" },
|
|
||||||
"name": {
|
|
||||||
"type": "string",
|
|
||||||
"not": "deprecated"
|
|
||||||
},
|
|
||||||
"hp": { "type": "integer" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
""");
|
|
||||||
|
|
||||||
var loader = new YamlConfigLoader(_rootPath)
|
|
||||||
.RegisterTable<int, MonsterConfigStub>("monster", "monster", "schemas/monster.schema.json",
|
|
||||||
static config => config.Id);
|
|
||||||
var registry = new ConfigRegistry();
|
|
||||||
|
|
||||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
|
||||||
{
|
|
||||||
Assert.That(exception, Is.Not.Null);
|
|
||||||
Assert.That(exception!.Diagnostic.FailureKind, Is.EqualTo(ConfigLoadFailureKind.SchemaUnsupported));
|
|
||||||
Assert.That(exception.Diagnostic.DisplayPath, Is.EqualTo("name"));
|
|
||||||
Assert.That(exception.Message, Does.Contain("must declare 'not' as an object-valued schema"));
|
|
||||||
Assert.That(registry.Count, Is.EqualTo(0));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 验证运行时 schema 校验与 JS 工具对反向引用模式保持一致。
|
/// 验证运行时 schema 校验与 JS 工具对反向引用模式保持一致。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -670,6 +670,17 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (element.TryGetProperty("not", out var notElement) &&
|
||||||
|
notElement.ValueKind == JsonValueKind.Object &&
|
||||||
|
!TryValidateStringFormatMetadataRecursively(
|
||||||
|
filePath,
|
||||||
|
$"{displayPath}[not]",
|
||||||
|
notElement,
|
||||||
|
out diagnostic))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.Equals(schemaType, "array", StringComparison.Ordinal))
|
if (!string.Equals(schemaType, "array", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
@ -693,17 +704,6 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (element.TryGetProperty("not", out var notElement) &&
|
|
||||||
notElement.ValueKind == JsonValueKind.Object &&
|
|
||||||
!TryValidateStringFormatMetadataRecursively(
|
|
||||||
filePath,
|
|
||||||
$"{displayPath}[not]",
|
|
||||||
notElement,
|
|
||||||
out diagnostic))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1856,6 +1856,39 @@ reward:
|
|||||||
assert.deepEqual(diagnostics, []);
|
assert.deepEqual(diagnostics, []);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("validateParsedConfig should reject objects that fully match a forbidden not schema", () => {
|
||||||
|
const schema = parseSchemaContent(`
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"reward": {
|
||||||
|
"type": "object",
|
||||||
|
"not": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["gold"],
|
||||||
|
"properties": {
|
||||||
|
"gold": { "type": "integer" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"gold": { "type": "integer" },
|
||||||
|
"bonus": { "type": "integer" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
const yaml = parseTopLevelYaml(`
|
||||||
|
reward:
|
||||||
|
gold: 10
|
||||||
|
`);
|
||||||
|
|
||||||
|
const diagnostics = validateParsedConfig(schema, yaml);
|
||||||
|
|
||||||
|
assert.equal(diagnostics.length, 1);
|
||||||
|
assert.match(diagnostics[0].message, /not/u);
|
||||||
|
});
|
||||||
|
|
||||||
test("applyFormUpdates should update nested scalar and scalar-array paths", () => {
|
test("applyFormUpdates should update nested scalar and scalar-array paths", () => {
|
||||||
const updated = applyFormUpdates(
|
const updated = applyFormUpdates(
|
||||||
[
|
[
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user