mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-07 00:39:00 +08:00
231 lines
6.6 KiB
JavaScript
231 lines
6.6 KiB
JavaScript
// Copyright (c) 2025-2026 GeWuYou
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
const test = require("node:test");
|
|
const assert = require("node:assert/strict");
|
|
const {
|
|
createSampleConfigYaml,
|
|
parseSchemaContent,
|
|
parseTopLevelYaml,
|
|
validateParsedConfig
|
|
} = require("../src/configValidation");
|
|
|
|
test("parseSchemaContent should capture object and array enum comparable metadata", () => {
|
|
const schema = parseSchemaContent(`
|
|
{
|
|
"type": "object",
|
|
"properties": {
|
|
"reward": {
|
|
"type": "object",
|
|
"properties": {
|
|
"gold": { "type": "integer" },
|
|
"itemId": { "type": "string" }
|
|
},
|
|
"enum": [
|
|
{ "gold": 10, "itemId": "potion" }
|
|
]
|
|
},
|
|
"dropItemIds": {
|
|
"type": "array",
|
|
"items": { "type": "string" },
|
|
"enum": [
|
|
["fire", "ice"]
|
|
]
|
|
}
|
|
}
|
|
}
|
|
`);
|
|
|
|
assert.deepEqual(schema.properties.reward.enumDisplayValues, ["{\"gold\":10,\"itemId\":\"potion\"}"]);
|
|
assert.match(schema.properties.reward.enumComparableValues[0], /^4:gold=/u);
|
|
assert.deepEqual(schema.properties.dropItemIds.enumDisplayValues, ["[\"fire\",\"ice\"]"]);
|
|
assert.equal(schema.properties.dropItemIds.enumComparableValues[0], "[13:string:4:fire,12:string:3:ice]");
|
|
});
|
|
|
|
test("validateParsedConfig should reject object values not declared in object enum", () => {
|
|
const schema = parseSchemaContent(`
|
|
{
|
|
"type": "object",
|
|
"required": ["reward"],
|
|
"properties": {
|
|
"reward": {
|
|
"type": "object",
|
|
"required": ["gold", "itemId"],
|
|
"properties": {
|
|
"gold": { "type": "integer" },
|
|
"itemId": { "type": "string" }
|
|
},
|
|
"enum": [
|
|
{ "gold": 10, "itemId": "potion" }
|
|
]
|
|
}
|
|
}
|
|
}
|
|
`);
|
|
const yaml = parseTopLevelYaml(`
|
|
reward:
|
|
gold: 10
|
|
itemId: elixir
|
|
`);
|
|
|
|
const diagnostics = validateParsedConfig(schema, yaml);
|
|
|
|
assert.equal(diagnostics.length, 1);
|
|
assert.match(diagnostics[0].message, /reward/u);
|
|
assert.match(diagnostics[0].message, /"itemId":"potion"/u);
|
|
});
|
|
|
|
test("validateParsedConfig should treat array enum candidates as order-sensitive", () => {
|
|
const schema = parseSchemaContent(`
|
|
{
|
|
"type": "object",
|
|
"required": ["dropItemIds"],
|
|
"properties": {
|
|
"dropItemIds": {
|
|
"type": "array",
|
|
"items": { "type": "string" },
|
|
"enum": [
|
|
["fire", "ice"]
|
|
]
|
|
}
|
|
}
|
|
}
|
|
`);
|
|
const yaml = parseTopLevelYaml(`
|
|
dropItemIds:
|
|
- ice
|
|
- fire
|
|
`);
|
|
|
|
const diagnostics = validateParsedConfig(schema, yaml);
|
|
|
|
assert.equal(diagnostics.length, 1);
|
|
assert.match(diagnostics[0].message, /dropItemIds/u);
|
|
assert.match(diagnostics[0].message, /\["fire","ice"\]/u);
|
|
});
|
|
|
|
test("validateParsedConfig should not add parent object enumMismatch after child diagnostics", () => {
|
|
const schema = parseSchemaContent(`
|
|
{
|
|
"type": "object",
|
|
"required": ["reward"],
|
|
"properties": {
|
|
"reward": {
|
|
"type": "object",
|
|
"required": ["gold", "itemId"],
|
|
"properties": {
|
|
"gold": { "type": "integer" },
|
|
"itemId": { "type": "string" }
|
|
},
|
|
"enum": [
|
|
{ "gold": 10, "itemId": "potion" }
|
|
]
|
|
}
|
|
}
|
|
}
|
|
`);
|
|
const yaml = parseTopLevelYaml(`
|
|
reward:
|
|
gold: wrong
|
|
itemId: potion
|
|
`);
|
|
|
|
const diagnostics = validateParsedConfig(schema, yaml);
|
|
|
|
assert.equal(diagnostics.length, 1);
|
|
assert.match(diagnostics[0].message, /reward\.gold/u);
|
|
assert.doesNotMatch(diagnostics[0].message, /must be one of|必须是以下值之一/u);
|
|
});
|
|
|
|
test("validateParsedConfig should not add parent array enumMismatch after item diagnostics", () => {
|
|
const schema = parseSchemaContent(`
|
|
{
|
|
"type": "object",
|
|
"required": ["dropLevels"],
|
|
"properties": {
|
|
"dropLevels": {
|
|
"type": "array",
|
|
"items": { "type": "integer" },
|
|
"enum": [
|
|
[1, 2]
|
|
]
|
|
}
|
|
}
|
|
}
|
|
`);
|
|
const yaml = parseTopLevelYaml(`
|
|
dropLevels:
|
|
- 1
|
|
- two
|
|
`);
|
|
|
|
const diagnostics = validateParsedConfig(schema, yaml);
|
|
|
|
assert.equal(diagnostics.length, 1);
|
|
assert.match(diagnostics[0].message, /dropLevels\[1\]/u);
|
|
assert.doesNotMatch(diagnostics[0].message, /must be one of|必须是以下值之一/u);
|
|
});
|
|
|
|
test("createSampleConfigYaml should reuse object and array enum payloads for valid samples", () => {
|
|
const schema = parseSchemaContent(`
|
|
{
|
|
"type": "object",
|
|
"required": ["reward", "phases"],
|
|
"properties": {
|
|
"reward": {
|
|
"type": "object",
|
|
"required": ["gold", "itemId"],
|
|
"properties": {
|
|
"gold": { "type": "integer" },
|
|
"itemId": { "type": "string" }
|
|
},
|
|
"enum": [
|
|
{ "gold": 10, "itemId": "potion" }
|
|
]
|
|
},
|
|
"phases": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "object",
|
|
"required": ["wave", "monsterId"],
|
|
"properties": {
|
|
"wave": { "type": "integer" },
|
|
"monsterId": { "type": "string" }
|
|
},
|
|
"enum": [
|
|
{ "wave": 1, "monsterId": "slime" }
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`);
|
|
|
|
const sample = createSampleConfigYaml(schema);
|
|
const yaml = parseTopLevelYaml(sample);
|
|
const diagnostics = validateParsedConfig(schema, yaml);
|
|
|
|
assert.equal(diagnostics.length, 0);
|
|
assert.match(sample, /^reward:$/mu);
|
|
assert.match(sample, /^ gold: 10$/mu);
|
|
assert.match(sample, /^ itemId: potion$/mu);
|
|
assert.match(sample, /^phases:$/mu);
|
|
assert.match(sample, /^ -$/mu);
|
|
assert.match(sample, /^ wave: 1$/mu);
|
|
assert.match(sample, /^ monsterId: slime$/mu);
|
|
});
|
|
|
|
test("parseSchemaContent should reject empty enum arrays", () => {
|
|
assert.throws(() => parseSchemaContent(`
|
|
{
|
|
"type": "object",
|
|
"properties": {
|
|
"rarity": {
|
|
"type": "string",
|
|
"enum": []
|
|
}
|
|
}
|
|
}
|
|
`), /must declare 'enum' with at least one value/u);
|
|
});
|