ModernUO/Projects/UOContent.Tests/Tests/Spells/Necromancy/BloodOathSpellTests.cs
Kamron Batman 9a25054809
test: remove trivial, duplicate and content-value tests (#2721)
## Summary
Many tests proved a specific change when it landed but don't need to run on every CI build forever. This removes **202 test methods and 9 files: 313 test cases, 2,247 → 1,934**. UOContent.Tests drops from about 14s to about 5s locally.

Removed:
- **Tautologies.** Asserting a constant, a constructor readback or a trivial getter, or testing BCL behavior.
- **Exact duplicates** of a surviving test on the same code path.
- **Obsolete regressions** whose failure can no longer be expressed.
- **Game-content values** shard owners customize (era drop rates, loot and skill tables, single-creature tuning). Each customization would otherwise force a test fix.

## How it was decided
1. Every test was classified, with a reason, by a per-shard sweep over all 287 test files.
2. Each proposed deletion was then checked by a second, adversarial reviewer against the surviving tests and the production code.
3. Anything that was the only coverage of a separate code path was **kept**, even where it looked like a duplicate. Examples: the unsigned `SpanReader`/`SpanWriter` methods, the at-location versus bounds enumerators, null-map enumerables, the security filters, archive-journal state transitions. About 130 proposed deletions were rejected this way.

Whole files removed:
- `FishingTests`, apart from 3 tests: the map-edge scan, serpent cleanup, and SOS chest consumption. The SOS test now stops at the first chest instead of measuring a catch rate. The multi-million-cast rate simulations are gone.
- `LordOaksTests` and `MeerEnragedTests` (single-creature tests).
- `AutoArchiveHelperTests`, `JsonDiscoverableTypeAttributeTests`, `DecimalSerializationTests` and `EnumConversionTests` (tautologies, or tests of their own local helpers).
- `NetworkStatsTests`.
- `MigratedDataLoadTests` (a subset of `AllSpawnFilesLoadTests`).
- `SpawnerFixtureCapture`: a capture tool, not a test, and its `version == 12` assert can no longer pass. The `.bin` fixtures it produced stay.

## Tests
- Server.Tests: 811 pass.
- UOContent.Tests: 1,123 pass.

A follow-up PR, stacked on this one, fixes how the remaining tests behave: shared state, CI-vacuous tests, harness duplication.
2026-10-18 20:08:19 -07:00

116 lines
4.1 KiB
C#

using System;
using Server.Mobiles;
using Server.Spells.Necromancy;
using Xunit;
namespace Server.Tests.Spells.Necromancy;
[Collection("Sequential UOContent Tests")]
public class BloodOathSpellTests
{
private static Mobile NewMobile()
{
var m = new Mobile(World.NewMobile);
m.DefaultMobileInit();
return m;
}
// Issue #1690: the real OSI duration is ((SpiritSpeak - Resist) / 8) + 8 seconds.
// ModernUO previously used /80 (matching the bugged in-game tooltip), which made
// Spirit Speak almost irrelevant to duration. RunUO/ServUO and the code's own
// fixed-point comment both use /8.
// Player caster: Publish 48 resist mitigation does NOT apply; the attacker takes the
// full reflected (original, un-bonused) damage.
// Creature caster (Publish 48 / SA+): ((Resist * 10) / 20) + 10 = % of reflected damage resisted.
[Theory]
[InlineData(40, 0.0, 36)] // 10% resisted -> 40 * 0.90
[InlineData(40, 100.0, 16)] // 60% resisted -> 40 * 0.40
[InlineData(40, 120.0, 12)] // 70% resisted -> 40 * 0.30
public void ComputeReflectedDamage_WithMitigation_ReducesByResist(int dmg, double resist, int expected)
{
Assert.Equal(expected, BloodOathSpell.ComputeReflectedDamage(dmg, resist, applyResistMitigation: true));
}
// The cursed target reflects to the caster; the caster attacking other mobiles must not reflect.
[Fact]
public void RegisterOath_BindsTargetToCaster_NotCasterToSelf()
{
var caster = NewMobile();
var target = NewMobile();
BloodOathSpell.RegisterOath(caster, target, TimeSpan.FromMinutes(5));
Assert.Equal(caster, BloodOathSpell.GetBloodOath(target));
Assert.Null(BloodOathSpell.GetBloodOath(caster));
BloodOathSpell.RemoveCurse(target);
caster.Delete();
target.Delete();
}
// Death/delete of either party removes the oath, so RemoveCurse must resolve from either
// the caster or the target key (the death hooks call it with `this`).
[Fact]
public void RemoveCurse_ByCaster_ClearsBothEntries()
{
var caster = NewMobile();
var target = NewMobile();
BloodOathSpell.RegisterOath(caster, target, TimeSpan.FromMinutes(5));
Assert.True(BloodOathSpell.RemoveCurse(caster));
Assert.Null(BloodOathSpell.GetBloodOath(target));
Assert.False(BloodOathSpell.RemoveCurse(target)); // already removed
caster.Delete();
target.Delete();
}
// Issue #1690: death/delete of either party must break the oath immediately. The oath is wired
// to the central PlayerMobile/BaseCreature death+delete events instead of a polling timer.
[Fact]
public void PlayerDeletedEvent_BreaksOath()
{
var caster = new PlayerMobile(World.NewMobile);
caster.DefaultMobileInit();
var target = new PlayerMobile(World.NewMobile);
target.DefaultMobileInit();
BloodOathSpell.RegisterOath(caster, target, TimeSpan.FromMinutes(5));
PlayerMobile.PlayerDeletedEvent(caster); // central handler breaks the oath from the caster side
Assert.Null(BloodOathSpell.GetBloodOath(target));
Assert.False(BloodOathSpell.RemoveCurse(target));
caster.Delete();
target.Delete();
}
[Fact]
public void CreatureDeletedEvent_BreaksOath()
{
var caster = new PlayerMobile(World.NewMobile);
caster.DefaultMobileInit();
var target = new TestCreature(World.NewMobile);
target.DefaultMobileInit();
BloodOathSpell.RegisterOath(caster, target, TimeSpan.FromMinutes(5));
CreatureEvents.CreatureDeletedEvent(target); // central handler breaks the oath from the target side
Assert.Null(BloodOathSpell.GetBloodOath(target));
Assert.False(BloodOathSpell.RemoveCurse(caster));
caster.Delete();
target.Delete();
}
private class TestCreature : BaseCreature
{
// Serial ctor skips AI/speed-table setup, which the test fixture does not configure.
public TestCreature(Serial serial) : base(serial)
{
}
}
}