using System.Text; using Microsoft.Extensions.Logging.Abstractions; using NeonSprawl.Server.Game.Skills; using Xunit; namespace NeonSprawl.Server.Tests.Game.Skills; public sealed class ContentBackedSkillLevelCurveTests { private const string LevelCurveSchemaJson = """ { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "additionalProperties": false, "required": ["schemaVersion", "levels"], "properties": { "schemaVersion": { "type": "integer", "const": 1 }, "levels": { "type": "array", "minItems": 1, "items": { "type": "object", "additionalProperties": false, "required": ["level", "requiredXp"], "properties": { "level": { "type": "integer", "minimum": 1 }, "requiredXp": { "type": "integer", "minimum": 0 } } } } } } """; private static ContentBackedSkillLevelCurve BuildCurve() => new( [ new ContentBackedSkillLevelCurve.LevelThresholdRow(1, 0), new ContentBackedSkillLevelCurve.LevelThresholdRow(2, 100), new ContentBackedSkillLevelCurve.LevelThresholdRow(3, 250), new ContentBackedSkillLevelCurve.LevelThresholdRow(4, 450), ]); [Theory] [InlineData(-5, 1)] [InlineData(0, 1)] [InlineData(99, 1)] [InlineData(100, 2)] [InlineData(249, 2)] [InlineData(250, 3)] [InlineData(449, 3)] [InlineData(450, 4)] [InlineData(9999, 4)] public void LevelFromTotalXp_ShouldResolveExpectedLevelAcrossBoundaries(int xp, int expectedLevel) { // Arrange var curve = BuildCurve(); // Act var level = curve.LevelFromTotalXp(xp); // Assert Assert.Equal(expectedLevel, level); } [Fact] public void Load_ShouldThrow_WhenCurveHasUnexpectedTopLevelProperty() { // Arrange var root = Directory.CreateTempSubdirectory("neon-sprawl-level-curve-"); try { var skillsDir = Path.Combine(root.FullName, "content", "skills"); var schemaDir = Path.Combine(root.FullName, "content", "schemas"); Directory.CreateDirectory(skillsDir); Directory.CreateDirectory(schemaDir); var curvePath = Path.Combine(skillsDir, "prototype_level_curve.json"); var schemaPath = Path.Combine(schemaDir, "level-curve.schema.json"); File.WriteAllText(schemaPath, LevelCurveSchemaJson, Encoding.UTF8); File.WriteAllText( curvePath, """ { "schemaVersion": 1, "levels": [ { "level": 1, "requiredXp": 0 }, { "level": 2, "requiredXp": 100 } ], "extraField": "should-fail" } """, Encoding.UTF8); // Act var ex = Record.Exception(() => ContentBackedSkillLevelCurve.Load(skillsDir, curvePath, schemaPath, NullLogger.Instance)); // Assert var ioe = Assert.IsType(ex); Assert.Contains("extraField", ioe.Message, StringComparison.Ordinal); } finally { try { Directory.Delete(root.FullName, recursive: true); } catch (IOException) { // Best-effort cleanup only. } } } }