Files
TheriapolisV3/Theriapolis.Tests/Data/ContentLoadTests.cs
T
Christopher Wiebe b451f83174 Initial commit: Theriapolis baseline at port/godot branch point
Captures the pre-Godot-port state of the codebase. This is the rollback
anchor for the Godot port (M0 of theriapolis-rpg-implementation-plan-godot-port.md).
All Phase 0 through Phase 6.5 work is included; Phase 7 is in flight.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 20:40:51 -07:00

193 lines
6.9 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Theriapolis.Core.Data;
using Theriapolis.Core.Rules.Stats;
using Xunit;
namespace Theriapolis.Tests.Data;
/// <summary>
/// End-to-end content load + cross-file integrity tests for Phase 5 JSON.
/// If a JSON edit breaks any of these, ContentValidate will also fail in CI.
/// </summary>
public sealed class ContentLoadTests
{
private static ContentLoader Loader() => new(TestHelpers.DataDirectory);
[Fact]
public void AllPhase5ContentFiles_LoadCleanly()
{
var loader = Loader();
var clades = loader.LoadClades();
var species = loader.LoadSpecies(clades);
var classes = loader.LoadClasses();
var subs = loader.LoadSubclasses(classes);
var bgs = loader.LoadBackgrounds();
var items = loader.LoadItems();
var npcs = loader.LoadNpcTemplates(items);
Assert.Equal(7, clades.Length);
Assert.True(species.Length >= 19, $"expected ≥19 species, got {species.Length}");
Assert.Equal(8, classes.Length);
Assert.Equal(16, subs.Length); // 8 classes × 2 subclasses
Assert.Equal(12, bgs.Length);
Assert.True(items.Length >= 30, $"expected ≥30 items, got {items.Length}");
Assert.True(npcs.Templates.Length >= 9, $"expected ≥9 NPC templates, got {npcs.Templates.Length}");
}
[Fact]
public void EveryClass_HasLevel1FeaturesDefined()
{
var classes = Loader().LoadClasses();
foreach (var c in classes)
{
var lv1 = Array.Find(c.LevelTable, e => e.Level == 1);
Assert.NotNull(lv1);
Assert.NotEmpty(lv1!.Features);
foreach (var feat in lv1.Features)
Assert.True(c.FeatureDefinitions.ContainsKey(feat),
$"Class '{c.Id}' level 1 references undefined feature '{feat}'");
}
}
[Fact]
public void EveryClass_HasFullLevelTable()
{
var classes = Loader().LoadClasses();
foreach (var c in classes)
{
var levels = c.LevelTable.Select(e => e.Level).OrderBy(x => x).ToArray();
Assert.Equal(20, levels.Length);
for (int lv = 1; lv <= 20; lv++)
Assert.Contains(lv, levels);
}
}
[Fact]
public void EveryClass_LevelTableProficiencyBonusMatchesD20()
{
var classes = Loader().LoadClasses();
foreach (var c in classes)
foreach (var entry in c.LevelTable)
Assert.Equal(ProficiencyBonus.ForLevel(entry.Level), entry.ProficiencyBonus);
}
[Fact]
public void EveryClass_HasTwoSubclasses()
{
var classes = Loader().LoadClasses();
var subs = Loader().LoadSubclasses(classes);
foreach (var c in classes)
{
var matching = subs.Where(s => s.ClassId == c.Id).ToArray();
Assert.Equal(2, matching.Length);
// Subclass ids must match what the class declares
foreach (var sid in c.SubclassIds)
Assert.Contains(sid, matching.Select(s => s.Id));
}
}
[Fact]
public void EverySpecies_ReferencesARealClade()
{
var clades = Loader().LoadClades();
var species = Loader().LoadSpecies(clades);
var cladeIds = clades.Select(c => c.Id).ToHashSet(StringComparer.OrdinalIgnoreCase);
foreach (var sp in species)
Assert.Contains(sp.CladeId, cladeIds);
}
[Theory]
[InlineData("wolf", 1, 0, 1, 0, 1, 0)]
[InlineData("fox", 0, 1, 1, 0, 1, 0)]
[InlineData("coyote", 0, 0, 1, 0, 1, 1)]
[InlineData("lion", 1, 1, 0, 0, 0, 1)]
[InlineData("leopard", 0, 2, 0, 0, 0, 1)]
[InlineData("housecat", 0, 1, 0, 1, 0, 1)]
[InlineData("ferret", 0, 1, 0, 1, 0, 1)]
[InlineData("badger", 0, 1, 1, 1, 0, 0)]
[InlineData("wolverine", 1, 1, 0, 1, 0, 0)]
[InlineData("brown_bear", 1,-1, 2, 0, 0, 0)]
[InlineData("polar_bear", 0,-1, 2, 0, 1, 0)]
[InlineData("elk", 1, 1, 0, 0, 1, 0)]
[InlineData("deer", 0, 2, 0, 0, 1, 0)]
[InlineData("moose", 0, 1, 1, 0, 1, 0)]
[InlineData("rabbit", -1, 2, 0, 0, 1, 0)]
[InlineData("hare", -1, 2, 1, 0, 0, 0)]
[InlineData("bull", 2, 0, 1, 0, 0, 0)]
[InlineData("ram", 1, 0, 1, 0, 1, 0)]
[InlineData("bison", 1, 0, 2, 0, 0, 0)]
public void Clade_Plus_Species_AbilityMods_MatchQuickRefTable(
string speciesId, int str, int dex, int con, int @int, int wis, int cha)
{
var loader = Loader();
var clades = loader.LoadClades();
var species = loader.LoadSpecies(clades);
var sp = species.Single(s => s.Id == speciesId);
var cl = clades.Single(c => c.Id == sp.CladeId);
int Sum(string ability) =>
(cl.AbilityMods.TryGetValue(ability, out var c) ? c : 0) +
(sp.AbilityMods.TryGetValue(ability, out var s) ? s : 0);
Assert.Equal(str, Sum("STR"));
Assert.Equal(dex, Sum("DEX"));
Assert.Equal(con, Sum("CON"));
Assert.Equal(@int, Sum("INT"));
Assert.Equal(wis, Sum("WIS"));
Assert.Equal(cha, Sum("CHA"));
}
[Fact]
public void EveryWeapon_HasDamageAndType()
{
var items = Loader().LoadItems();
foreach (var i in items.Where(i => i.Kind == "weapon"))
{
Assert.False(string.IsNullOrWhiteSpace(i.Damage), $"weapon '{i.Id}' missing damage");
Assert.False(string.IsNullOrWhiteSpace(i.DamageType), $"weapon '{i.Id}' missing damage_type");
}
}
[Fact]
public void EveryArmor_HasPositiveAcBase()
{
var items = Loader().LoadItems();
foreach (var i in items.Where(i => i.Kind == "armor"))
Assert.True(i.AcBase > 0, $"armor '{i.Id}' has non-positive ac_base");
}
[Fact]
public void NpcZoneTable_HasOneEntryPerZone()
{
var items = Loader().LoadItems();
var npcs = Loader().LoadNpcTemplates(items);
int expected = Theriapolis.Core.C.DANGER_ZONE_MAX - Theriapolis.Core.C.DANGER_ZONE_MIN + 1;
foreach (var (kind, byZone) in npcs.SpawnKindToTemplateByZone)
Assert.Equal(expected, byZone.Length);
}
[Fact]
public void NpcZoneTable_AllReferencedTemplatesExist()
{
var items = Loader().LoadItems();
var npcs = Loader().LoadNpcTemplates(items);
var ids = npcs.Templates.Select(t => t.Id).ToHashSet();
foreach (var (_, byZone) in npcs.SpawnKindToTemplateByZone)
foreach (var tid in byZone)
Assert.Contains(tid, ids);
}
[Fact]
public void EveryNpcTemplate_HasPositiveHpAndAc()
{
var items = Loader().LoadItems();
var npcs = Loader().LoadNpcTemplates(items);
foreach (var t in npcs.Templates)
{
Assert.True(t.Hp > 0, $"NPC '{t.Id}' has non-positive HP");
Assert.True(t.Ac > 0, $"NPC '{t.Id}' has non-positive AC");
}
}
}