// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
using System.IO;
using GFramework.Game.Config;
namespace GFramework.Game.Tests.Config;
///
/// 验证内部 schema 解析器会输出稳定且可预期的运行时依赖元数据。
///
[TestFixture]
public sealed class YamlConfigSchemaValidatorTests
{
private string? _rootPath;
///
/// 为每个测试准备独立临时目录。
///
[SetUp]
public void SetUp()
{
_rootPath = Path.Combine(Path.GetTempPath(), "GFramework.SchemaValidatorTests", Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(_rootPath);
}
///
/// 清理测试临时目录。
///
[TearDown]
public void TearDown()
{
if (!string.IsNullOrEmpty(_rootPath) &&
Directory.Exists(_rootPath))
{
Directory.Delete(_rootPath, true);
}
}
///
/// 验证 schema 中声明的跨表引用名称会以序数排序形式输出,
/// 避免热重载依赖推导与测试快照受哈希集合枚举顺序影响。
///
[Test]
public void Load_Should_Return_Referenced_Table_Names_In_Ordinal_Sorted_Order()
{
var schemaPath = CreateSchemaFile(
"schemas/monster.schema.json",
"""
{
"type": "object",
"properties": {
"weaponId": {
"type": "string",
"x-gframework-ref-table": "weapon"
},
"allies": {
"type": "array",
"items": {
"type": "integer",
"x-gframework-ref-table": "ally"
}
},
"itemId": {
"type": "string",
"x-gframework-ref-table": "item"
}
}
}
""");
var schema = YamlConfigSchemaValidator.Load("monster", schemaPath);
Assert.That(schema.ReferencedTableNames, Is.EqualTo(new[] { "ally", "item", "weapon" }));
}
///
/// 验证条件子 schema 复用同一条 ref-table 字段时,不会把同一引用重复写入结果。
///
[Test]
public void ValidateAndCollectReferences_Should_Not_Duplicate_Reference_Usages_From_DependentSchemas()
{
var schemaPath = CreateSchemaFile(
"schemas/monster.schema.json",
"""
{
"type": "object",
"properties": {
"reward": {
"type": "object",
"properties": {
"itemId": {
"type": "string",
"x-gframework-ref-table": "item"
}
},
"dependentSchemas": {
"itemId": {
"type": "object",
"properties": {
"itemId": {
"type": "string",
"x-gframework-ref-table": "item"
}
}
}
}
}
}
}
""");
var schema = YamlConfigSchemaValidator.Load("monster", schemaPath);
var references = YamlConfigSchemaValidator.ValidateAndCollectReferences(
"monster",
schema,
"monster/slime.yaml",
"""
reward:
itemId: potion
""");
Assert.That(references, Has.Count.EqualTo(1));
Assert.Multiple(() =>
{
Assert.That(references[0].DisplayPath, Is.EqualTo("reward.itemId"));
Assert.That(references[0].ReferencedTableName, Is.EqualTo("item"));
Assert.That(references[0].RawValue, Is.EqualTo("potion"));
});
}
///
/// 验证 allOf focused block 复用同一条 ref-table 字段时,不会把同一引用重复写入结果。
///
[Test]
public void ValidateAndCollectReferences_Should_Not_Duplicate_Reference_Usages_From_AllOf()
{
var schemaPath = CreateSchemaFile(
"schemas/monster.schema.json",
"""
{
"type": "object",
"properties": {
"reward": {
"type": "object",
"properties": {
"itemId": {
"type": "string",
"x-gframework-ref-table": "item"
}
},
"allOf": [
{
"type": "object",
"properties": {
"itemId": {
"type": "string",
"x-gframework-ref-table": "item"
}
}
}
]
}
}
}
""");
var schema = YamlConfigSchemaValidator.Load("monster", schemaPath);
var references = YamlConfigSchemaValidator.ValidateAndCollectReferences(
"monster",
schema,
"monster/slime.yaml",
"""
reward:
itemId: potion
""");
Assert.That(references, Has.Count.EqualTo(1));
Assert.Multiple(() =>
{
Assert.That(references[0].DisplayPath, Is.EqualTo("reward.itemId"));
Assert.That(references[0].ReferencedTableName, Is.EqualTo("item"));
Assert.That(references[0].RawValue, Is.EqualTo("potion"));
});
}
///
/// 在临时目录中创建 schema 文件。
///
/// 相对根目录的路径。
/// 文件内容。
/// 写入后的绝对路径。
private string CreateSchemaFile(
string relativePath,
string content)
{
if (_rootPath is null)
{
throw new InvalidOperationException("Root path is not initialized.");
}
var fullPath = Path.Combine(_rootPath, relativePath.Replace('/', Path.DirectorySeparatorChar));
var directoryPath = Path.GetDirectoryName(fullPath);
if (!string.IsNullOrWhiteSpace(directoryPath))
{
Directory.CreateDirectory(directoryPath);
}
File.WriteAllText(fullPath, content.Replace("\n", Environment.NewLine, StringComparison.Ordinal));
return fullPath;
}
}