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.
This commit is contained in:
Kamron Batman 2026-10-18 20:08:19 -07:00 • committed by GitHub
parent 078be52777
commit 9a25054809
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
89 changed files with 11 additions and 4603 deletions

View file

@ -56,25 +56,6 @@ public class RawInterpolatedStringHandlerTests
handler.Clear();
}
[Fact]
public void TestNoFormatPreservesCase()
{
var handler = new RawInterpolatedStringHandler(0, 1);
handler.AppendFormatted("Hello WORLD");
Assert.Equal("Hello WORLD", handler.Text.ToString());
handler.Clear();
}
[Fact]
public void TestUnicodeLowercase()
{
// ToLowerInvariant on Greek letter
var handler = new RawInterpolatedStringHandler(0, 1);
handler.AppendFormatted("ΑΒΓ", format: "L");
Assert.Equal("αβγ", handler.Text.ToString());
handler.Clear();
}
[Fact]
public void TestLowercaseFormatSurrogatePairAtChunkBoundary()
{

View file

@ -497,60 +497,6 @@ public class SpanReaderTests
Assert.Equal(3, reader.Position);
}
[Fact]
public void TestLength()
{
ReadOnlySpan<byte> buffer = [0x01, 0x02, 0x03, 0x04, 0x05];
var reader = new SpanReader(buffer);
Assert.Equal(5, reader.Length);
}
[Fact]
public void TestPosition()
{
ReadOnlySpan<byte> buffer = [0x01, 0x02, 0x03];
var reader = new SpanReader(buffer);
Assert.Equal(0, reader.Position);
reader.ReadByte();
Assert.Equal(1, reader.Position);
reader.ReadUInt16();
Assert.Equal(3, reader.Position);
}
[Fact]
public void TestRemaining()
{
ReadOnlySpan<byte> buffer = [0x01, 0x02, 0x03, 0x04, 0x05];
var reader = new SpanReader(buffer);
Assert.Equal(5, reader.Remaining);
reader.ReadByte();
Assert.Equal(4, reader.Remaining);
reader.ReadUInt16();
Assert.Equal(2, reader.Remaining);
reader.ReadUInt16();
Assert.Equal(0, reader.Remaining);
}
[Fact]
public void TestBuffer()
{
ReadOnlySpan<byte> buffer = [0x01, 0x02, 0x03];
var reader = new SpanReader(buffer);
var bufferProperty = reader.Buffer;
Assert.Equal(3, bufferProperty.Length);
AssertThat.Equal(bufferProperty, buffer);
}
[Fact]
public void TestReadStringEmptyFixedLength()
{

View file

@ -75,19 +75,6 @@ public class SpanWriterTests
AssertThat.Equal(writer.Span, [1, 0]);
}
[Fact]
public void TestWriteByte()
{
Span<byte> buffer = stackalloc byte[2];
var writer = new SpanWriter(buffer);
writer.Write((byte)0x12);
writer.Write((byte)0x34);
Assert.Equal(2, writer.Position);
AssertThat.Equal(writer.Span, [0x12, 0x34]);
}
[Fact]
public void TestWriteSByte()
{
@ -235,19 +222,6 @@ public class SpanWriterTests
AssertThat.Equal(writer.Span, [0x01, 0x02, 0x03, 0x01, 0x02, 0x03]);
}
[Fact]
public void TestWriteAsciiChar()
{
Span<byte> buffer = stackalloc byte[2];
var writer = new SpanWriter(buffer);
writer.WriteAscii('A');
writer.WriteAscii('B');
Assert.Equal(2, writer.Position);
AssertThat.Equal(writer.Span, "AB"u8);
}
[Fact]
public void TestWriteAsciiString()
{
@ -457,16 +431,6 @@ public class SpanWriterTests
writer.Dispose();
}
[Fact]
public void TestEnsureCapacityNoResize()
{
Span<byte> buffer = stackalloc byte[10];
var writer = new SpanWriter(buffer);
writer.EnsureCapacity(10);
Assert.Equal(10, writer.Capacity);
}
[Fact]
public void TestEnsureCapacityNoResizeThrows()
{
@ -552,40 +516,4 @@ public class SpanWriterTests
writer.Write((byte)0xFF);
Assert.Equal(3, writer.BytesWritten);
}
[Fact]
public void TestCapacity()
{
Span<byte> buffer = stackalloc byte[10];
var writer = new SpanWriter(buffer);
Assert.Equal(10, writer.Capacity);
}
[Fact]
public void TestRawBuffer()
{
Span<byte> buffer = stackalloc byte[10];
var writer = new SpanWriter(buffer);
writer.Write((byte)0x01);
var raw = writer.RawBuffer;
Assert.Equal(10, raw.Length);
Assert.Equal(0x01, raw[0]);
}
[Fact]
public void TestSpan()
{
Span<byte> buffer = stackalloc byte[10];
var writer = new SpanWriter(buffer);
writer.Write((byte)0x01);
writer.Write((byte)0x02);
var span = writer.Span;
Assert.Equal(2, span.Length);
AssertThat.Equal(span, [0x01, 0x02]);
}
}

View file

@ -52,19 +52,6 @@ public class ValueStringBuilderTests
// writes into the shared buffer, and Append() reconciles via `this = handler._builder`.
// Critical to detect if C# compiler codegen changes break this assumption.
[Fact]
public void Interpolation_Stackalloc_NoGrow()
{
// Stackalloc buffer large enough — no Grow needed.
// Validates: copy's Span shares same stackalloc memory, _length is reconciled.
var sb = new ValueStringBuilder(stackalloc char[64]);
sb.Append($"Hello {42} world");
Assert.Equal("Hello 42 world", sb.ToString());
Assert.Equal(14, sb.Length);
sb.Dispose();
}
[Fact]
public void Interpolation_Stackalloc_WithGrow()
{
@ -82,20 +69,6 @@ public class ValueStringBuilderTests
sb.Dispose();
}
[Fact]
public void Interpolation_Stackalloc_PreExistingContent_NoGrow()
{
// Append plain text first, then interpolation. Buffer large enough.
// Validates: interpolation appends after existing content, doesn't overwrite.
var sb = new ValueStringBuilder(stackalloc char[64]);
sb.Append("prefix-");
sb.Append($"value={99}");
Assert.Equal("prefix-value=99", sb.ToString());
Assert.Equal(15, sb.Length);
sb.Dispose();
}
[Fact]
public void Interpolation_Stackalloc_PreExistingContent_WithGrow()
{
@ -264,20 +237,6 @@ public class ValueStringBuilderTests
sb.Dispose();
}
[Fact]
public void Interpolation_Stackalloc_DisposeAfterGrow()
{
// After Grow moves stackalloc to pool, Dispose should return the pooled array.
// Validates: _arrayToReturnToPool is correctly reconciled so Dispose works.
var sb = new ValueStringBuilder(stackalloc char[4]);
sb.Append($"grow beyond stackalloc: {12345}");
// If _arrayToReturnToPool wasn't reconciled, Dispose would either
// not return the pooled array (leak) or return null (no-op when it shouldn't be).
sb.Dispose();
// No assertion needed — if _arrayToReturnToPool was wrong, pool corruption
// would surface as test failures elsewhere. The test passing = no crash.
}
[Fact]
public void Interpolation_Heap_DoubleGrow()
{

View file

@ -16,13 +16,6 @@ public class ValueLinkListTests
public TestEntity(int serial) => Serial = serial;
}
[Fact]
public void TestDefaultListIsEmpty()
{
var linkList = new ValueLinkList<TestEntity>();
Assert.Equal(0, linkList.Count);
}
[Fact]
public void TestAddingElementsFirst()
{
@ -397,29 +390,4 @@ public class ValueLinkListTests
}
);
}
[Fact]
public void TestThrowsIfModifiedWhileIteratingNested()
{
Assert.Throws<InvalidOperationException>(
() =>
{
var linkList = new ValueLinkList<TestEntity>();
var entity1 = new TestEntity(1);
var entity2 = new TestEntity(2);
linkList.AddFirst(entity1);
linkList.AddLast(entity2);
foreach (var item in linkList)
{
foreach (var nestedItem in linkList)
{
linkList.Remove(nestedItem);
}
}
}
);
}
}

View file

@ -16,31 +16,6 @@ public class Point2DTests
Assert.Equal($"({int.MaxValue}, {int.MinValue})", new Point2D(int.MaxValue, int.MinValue).ToString());
}
[Fact]
public void TestPoint2DTryFormatSucceeds()
{
var array = new char[128];
var p1 = new Point2D(0, 0);
Assert.True(p1.TryFormat(array, out var cp1, null, null));
Assert.Equal(6, cp1);
Array.Clear(array);
var p2 = new Point2D(1, 1);
Assert.True(p2.TryFormat(array, out var cp2, null, null));
Assert.Equal(6, cp2);
Array.Clear(array);
var p3 = new Point2D(int.MaxValue, int.MaxValue);
Assert.True(p3.TryFormat(array, out var cp3, null, null));
Assert.Equal(4 + 20, cp3);
Array.Clear(array);
var p4 = new Point2D(int.MinValue, int.MinValue);
Assert.True(p4.TryFormat(array, out var cp4, null, null));
Assert.Equal(4 + 22, cp4);
}
[Fact]
public void TestPoint2DTryFormatFails()
{

View file

@ -27,33 +27,6 @@ public class Point3DTests
Assert.Equal($"({min}, {min}, {min})", new Point3D(min, min, min).ToString());
}
[Fact]
public void TestPoint3DTryFormatSucceeds()
{
const int max = int.MaxValue;
const int min = int.MinValue;
var array = new char[128];
var p1 = new Point3D(0, 0, 0);
Assert.True(p1.TryFormat(array, out var cp1, null, null));
Assert.Equal(6 + 3, cp1);
Array.Clear(array);
var p2 = new Point3D(1, 1, 1);
Assert.True(p2.TryFormat(array, out var cp2, null, null));
Assert.Equal(6 + 3, cp2);
Array.Clear(array);
var p3 = new Point3D(max, max, max);
Assert.True(p3.TryFormat(array, out var cp3, null, null));
Assert.Equal(6 + 30, cp3);
Array.Clear(array);
var p4 = new Point3D(min, min, min);
Assert.True(p4.TryFormat(array, out var cp4, null, null));
Assert.Equal(6 + 33, cp4);
}
[Fact]
public void TestPoint3DTryFormatFails()
{

View file

@ -16,33 +16,6 @@ public class Rectangle2DTests
Assert.Equal($"({min}, {min})+({min}, {min})", new Rectangle2D(min, min, min, min).ToString());
}
[Fact]
public void TestRectangle2DTryFormatSucceeds()
{
const int max = int.MaxValue;
const int min = int.MinValue;
var array = new char[128];
var p1 = new Rectangle2D(0, 0, 0, 0);
Assert.True(p1.TryFormat(array, out var cp1, null, null));
Assert.Equal(9 + 4, cp1);
Array.Clear(array);
var p2 = new Rectangle2D(1, 1, 1, 1);
Assert.True(p2.TryFormat(array, out var cp2, null, null));
Assert.Equal(9 + 4, cp2);
Array.Clear(array);
var p3 = new Rectangle2D(max, max, max, max);
Assert.True(p3.TryFormat(array, out var cp3, null, null));
Assert.Equal(9 + 4 * 10, cp3);
Array.Clear(array);
var p4 = new Rectangle2D(min, min, min, min);
Assert.True(p4.TryFormat(array, out var cp4, null, null));
Assert.Equal(9 + 4 * 11, cp4);
}
[Fact]
public void TestRectangle2DTryFormatFails()
{

View file

@ -16,33 +16,6 @@ public class Rectangle3DTests
Assert.Equal($"({min}, {min}, {min})+({min}, {min}, {min})", new Rectangle3D(min, min, min, min, min, min).ToString());
}
[Fact]
public void TestRectangle3DTryFormatSucceeds()
{
const int max = int.MaxValue;
const int min = int.MinValue;
var array = new char[128];
var p1 = new Rectangle3D(0, 0, 0, 0, 0, 0);
Assert.True(p1.TryFormat(array, out var cp1, null, null));
Assert.Equal(13 + 6, cp1);
Array.Clear(array);
var p2 = new Rectangle3D(1, 1, 1, 1, 1, 1);
Assert.True(p2.TryFormat(array, out var cp2, null, null));
Assert.Equal(13 + 6, cp2);
Array.Clear(array);
var p3 = new Rectangle3D(max, max, max, max, max, max);
Assert.True(p3.TryFormat(array, out var cp3, null, null));
Assert.Equal(13 + 6 * 10, cp3);
Array.Clear(array);
var p4 = new Rectangle3D(min, min, min, min, min, min);
Assert.True(p4.TryFormat(array, out var cp4, null, null));
Assert.Equal(13 + 6 * 11, cp4);
}
[Fact]
public void TestRectangle3DTryFormatFails()
{

View file

@ -39,40 +39,6 @@ public sealed class WorldLocationTests
Assert.Equal($"({min}, {min}, {min}) [{_superLongMapName}]", new WorldLocation(min, min, min, map).ToString());
}
[Theory]
[InlineData(null)]
[InlineData("Internal Map")]
[InlineData(_superLongMapName)]
public void TestWorldLocationTryFormatSucceeds(string mapName)
{
var map = mapName != null ? CreateMap(mapName) : null;
var mapNameLength = mapName?.Length ?? _nullMap.Length;
const int max = int.MaxValue;
const int min = int.MinValue;
var array = new char[128];
var p1 = new WorldLocation(0, 0, 0, map);
Assert.True(p1.TryFormat(array, out var cp1, null, null));
Assert.Equal(9 + 3 + mapNameLength, cp1);
Array.Clear(array);
var p2 = new WorldLocation(1, 1, 1, map);
Assert.True(p2.TryFormat(array, out var cp2, null, null));
Assert.Equal(9 + 3 + mapNameLength, cp2);
Array.Clear(array);
array = new char[256]; // We need a bigger buffer!
var p3 = new WorldLocation(max, max, max, map);
Assert.True(p3.TryFormat(array, out var cp3, null, null));
Assert.Equal(9 + 30 + mapNameLength, cp3);
Array.Clear(array);
var p4 = new WorldLocation(min, min, min, map);
Assert.True(p4.TryFormat(array, out var cp4, null, null));
Assert.Equal(9 + 33 + mapNameLength, cp4);
}
[Theory]
[InlineData(null)]
[InlineData("Internal Map")]

View file

@ -66,27 +66,6 @@ public class ContainerChildSkipTests
}
// A skipped child is not saved, so the container must load without it.
[Fact]
public void SkippingContainer_LoadsWithoutItsSkippedChildren()
{
var pack = new SkippingContainer();
var item = new Item(0x1234);
pack.DropItem(item);
var writer = new BufferWriter(true);
pack.Serialize(writer);
var buffer = new byte[writer.Position];
writer.Buffer.AsSpan(0, (int)writer.Position).CopyTo(buffer);
item.Delete();
pack.Delete();
var copy = new SkippingContainer(World.NewItem);
copy.Deserialize(new BufferReader(buffer));
Assert.Empty(copy.Items);
}
// A written child serial could resolve at load to an unrelated item that was handed the same serial.
[Fact]
public void SkippingContainer_DoesNotWriteAliveChildSerial()

View file

@ -419,15 +419,6 @@ public class ContainerTests
Assert.Equal(0, c.GetBestGroupAmount(new[] { typeof(TestReagentA) }, true, HueGrouper));
}
[Fact]
public void TestGetBestGroupAmount_NullGrouper_Throws()
{
var c = MakeContainer(0x2C0);
Assert.Throws<ArgumentNullException>(
() => c.GetBestGroupAmount(new[] { typeof(TestReagentA) }, true, null)
);
}
[Fact]
public void TestGetAmount_TypeArray_SumsAcrossTypes()
{
@ -470,39 +461,6 @@ public class ContainerTests
Assert.Equal(1, s2.Amount);
}
[Fact]
public void TestConsumeTotal_LengthMismatch_Throws()
{
var c = MakeContainer(0x300);
Assert.Throws<ArgumentException>(
() => c.ConsumeTotal(new[] { typeof(TestReagentA) }, new[] { 1, 2 })
);
}
[Fact]
public void TestConsumeTotalGrouped_LengthMismatch_Throws()
{
var c = MakeContainer(0x310);
Assert.Throws<ArgumentException>(
() => c.ConsumeTotalGrouped(
new[] { typeof(TestReagentA) }, new[] { 1, 2 },
true, null, HueGrouper
)
);
}
[Fact]
public void TestConsumeTotalGrouped_NullGrouper_Throws()
{
var c = MakeContainer(0x320);
Assert.Throws<ArgumentNullException>(
() => c.ConsumeTotalGrouped(
new[] { typeof(TestReagentA) }, new[] { 1 },
true, null, null
)
);
}
// -------------------------------------------------------------------------
// FindItemsByType(Type) and FindItemsByType(ReadOnlySpan<Type>) — Phase 10:
// these used to allocate a Predicate<Item> per call (method-group conversion

View file

@ -21,32 +21,6 @@ public class DecayRegistrationTests
}
// The item is already dequeued, so a refusal that does not reschedule strands it forever.
[Fact]
public void ItemWhoseDecayIsRefused_StaysTrackedAndSurvives()
{
var start = Core._now;
try
{
var item = new RefusesDecayItem();
item.MoveToWorld(new Point3D(106, 100, 0), Map.Felucca);
AdvanceDecay(start, item.DecayTime + TimeSpan.FromMinutes(2), item);
Assert.False(item.Deleted, "A refused decay must not delete the item.");
Assert.True(
DecayScheduler.IsRegistered(item),
"A refused decay must leave the item tracked so it is retried."
);
item.Delete();
}
finally
{
Core._now = start;
}
}
// Drives the scheduler and the game clock without depending on the shared timer wheel.
private static void AdvanceDecay(DateTime start, TimeSpan duration, Item item)
{
@ -103,68 +77,9 @@ public class DecayRegistrationTests
}
}
[Fact]
public void ItemPlacedInWorld_IsRegisteredForDecay()
{
var item = new Item(0x1234);
item.MoveToWorld(new Point3D(100, 100, 0), Map.Felucca);
Assert.True(item.CanDecay());
Assert.True(DecayScheduler.IsRegistered(item), "Item on the ground must be tracked for decay.");
item.Delete();
}
// The real client path: Mobile.Lift internalizes, then Mobile.Drop calls MoveToWorld.
[Fact]
public void ItemLiftedThenDroppedToGround_IsRegisteredForDecay()
{
var item = new Item(0x1234);
item.MoveToWorld(new Point3D(100, 100, 0), Map.Felucca);
// Player lifts the item to the cursor.
item.Internalize();
Assert.Equal(Map.Internal, item.Map);
// Player drops it back onto the ground.
item.MoveToWorld(new Point3D(101, 100, 0), Map.Felucca);
Assert.True(item.CanDecay());
Assert.True(
DecayScheduler.IsRegistered(item),
"Item dropped to the ground after being lifted must be tracked for decay."
);
item.Delete();
}
// Held on the cursor means Map.Internal, which must not be tracked.
[Fact]
public void ItemLiftedToCursor_IsNotRegisteredForDecay()
{
var item = new Item(0x1234);
item.MoveToWorld(new Point3D(100, 100, 0), Map.Felucca);
item.Internalize();
Assert.False(item.CanDecay());
Assert.False(DecayScheduler.IsRegistered(item), "Item held on the cursor must not be tracked for decay.");
item.Delete();
}
// A new item is on Map.Internal, so construction must not touch the scheduler.
[Fact]
public void NewItem_IsNotTracked()
{
var item = new Item(0x1234);
Assert.Equal(DecayScheduler.SlotNone, item.DecaySlot);
Assert.False(DecayScheduler.IsRegistered(item), "A newly constructed item must not be tracked.");
item.Delete();
}
// Unregister() trusts DecaySlot; IsRegistered() scans the structures. Assert both to catch drift.
[Fact]
public void DecaySlot_AgreesWithStructures_AcrossLifecycle()
@ -475,24 +390,4 @@ public class DecayRegistrationTests
}
// Dropping into a container must untrack; taking it back out to the ground must re-track.
[Fact]
public void ItemMovedIntoContainerThenBackToGround_IsRegisteredForDecay()
{
var pack = new Container(0xE75);
pack.MoveToWorld(new Point3D(100, 100, 0), Map.Felucca);
var item = new Item(0x1234);
pack.AddItem(item);
Assert.False(DecayScheduler.IsRegistered(item), "Item inside a container must not be tracked for decay.");
item.Internalize();
item.MoveToWorld(new Point3D(101, 100, 0), Map.Felucca);
Assert.True(item.CanDecay());
Assert.True(DecayScheduler.IsRegistered(item), "Item taken out of a container must be tracked for decay.");
item.Delete();
pack.Delete();
}
}

View file

@ -24,25 +24,6 @@ public class PlayerConstructedStackingTests
private static StackableItem MakeStack(Serial serial, int amount, bool playerConstructed) =>
new(serial) { Amount = amount, PlayerConstructed = playerConstructed };
[Theory]
[InlineData(false)]
[InlineData(true)]
public void CanStackWith_IsTrueWhenProvenanceMatches(bool playerConstructed)
{
var first = MakeStack((Serial)0x1, 5, playerConstructed);
var second = MakeStack((Serial)0x2, 7, playerConstructed);
try
{
Assert.True(first.CanStackWith(second));
}
finally
{
first.Delete();
second.Delete();
}
}
[Theory]
[InlineData(false)]
[InlineData(true)]

View file

@ -1,30 +0,0 @@
using Server.Json;
using Xunit;
namespace Server.Tests.Json;
public class JsonDiscoverableTypeAttributeTests
{
[JsonDiscoverableType]
private sealed class DefaultName { }
[JsonDiscoverableType("custom")]
private sealed class Overridden { }
[Fact]
public void DefaultDiscriminator_IsNull()
{
var attr = (JsonDiscoverableTypeAttribute)System.Attribute.GetCustomAttribute(
typeof(DefaultName), typeof(JsonDiscoverableTypeAttribute), false);
Assert.NotNull(attr);
Assert.Null(attr.Discriminator);
}
[Fact]
public void OverrideDiscriminator_IsReturned()
{
var attr = (JsonDiscoverableTypeAttribute)System.Attribute.GetCustomAttribute(
typeof(Overridden), typeof(JsonDiscoverableTypeAttribute), false);
Assert.Equal("custom", attr.Discriminator);
}
}

View file

@ -35,12 +35,4 @@ public class Rectangle3DConverterTests
Assert.DoesNotContain("z1", json);
Assert.DoesNotContain("z2", json);
}
[Fact]
public void HomeRangeBounds_WritesZ()
{
var json = JsonSerializer.Serialize(new Rectangle3D(0, 0, -128, 10, 10, 256), JsonConfig.DefaultOptions);
Assert.Contains("\"z1\":", json);
Assert.Contains("\"z2\":", json);
}
}

View file

@ -220,39 +220,6 @@ public class CanFitItemTests
}
}
[SkippableFact]
public void CanFitItem_CanPlaceOnTopOfImpassable()
{
SkipIfNoTileData();
Skip.If(ServerFixture.SurfaceImpassableTileId == 0, "No Surface+Impassable tile found in TileData");
var map = Map.Felucca;
const int x = 1950;
const int y = 1950;
TestMulti multi = null;
try
{
// Get the actual tile height from TileData
var tileData = TileData.ItemTable[ServerFixture.SurfaceImpassableTileId];
var tileHeight = tileData.CalcHeight;
const int tileZ = 10;
// Create a Surface+Impassable tile at Z=10
multi = CreateSurfaceImpassableMulti(map, new Point3D(x, y, 0), surfaceZ: tileZ);
// Place item on top should work
var surfaceTop = tileZ + tileHeight;
var result = map.CanFitItem(x, y, surfaceTop, 1);
Assert.True(result, $"Should be able to place item on top of Surface+Impassable tile at Z={surfaceTop}");
}
finally
{
multi?.Delete();
}
}
private TestMulti CreateSurfaceImpassableMulti(Map map, Point3D location, int surfaceZ)
{
// Use SurfaceImpassableTileId - a tile that has both Surface and Impassable flags

View file

@ -23,32 +23,6 @@ public class CanSpawnMobileTileDataTests
Skip.If(!ServerFixture.TileDataLoaded, "TileData not loaded - client files required");
}
[SkippableFact]
public void CanSpawnMobile_FindsSurfaceOnMulti()
{
SkipIfNoTileData();
var map = Map.Felucca;
const int x = 1100;
const int y = 1100;
TestMulti multi = null;
try
{
// Create a multi with a floor at Z=20 using a real surface tile
multi = CreateFloorMulti(map, new Point3D(x, y, 0), floorZ: 20);
var result = map.CanSpawnMobile(x, y, 15, 30, false, false, out var spawnZ);
Assert.True(result);
Assert.True(spawnZ >= 15 && spawnZ <= 30);
}
finally
{
multi?.Delete();
}
}
[SkippableFact]
public void CanSpawnMobile_FindsLowestValidSurface()
{
@ -239,51 +213,6 @@ public class CanSpawnMobileTileDataTests
}
}
[SkippableFact]
public void CanSpawnMobile_MalasBuilding_FindsGroundFloor()
{
SkipIfNoTileData();
var map = Map.Malas;
const int x = 991;
const int y = 519;
var result = map.CanSpawnMobile(x, y, -60, -40, false, false, out var spawnZ);
Assert.True(result);
Assert.True(spawnZ >= -60 && spawnZ <= -40, $"Expected ground floor around -50, got {spawnZ}");
}
[SkippableFact]
public void CanSpawnMobile_MalasBuilding_FindsSecondFloor()
{
SkipIfNoTileData();
var map = Map.Malas;
const int x = 991;
const int y = 519;
var result = map.CanSpawnMobile(x, y, -30, 0, false, false, out var spawnZ);
Assert.True(result);
Assert.True(spawnZ >= -30 && spawnZ <= 0, $"Expected second floor, got {spawnZ}");
}
[SkippableFact]
public void CanSpawnMobile_MalasBuilding_LowestFloorPreferred()
{
SkipIfNoTileData();
var map = Map.Malas;
const int x = 991;
const int y = 519;
var result = map.CanSpawnMobile(x, y, -60, 0, false, false, out var spawnZ);
Assert.True(result);
Assert.True(spawnZ <= -40, $"Expected lowest floor around -50, got {spawnZ}");
}
private TestMulti CreateFloorMulti(Map map, Point3D location, int floorZ)
{
var multi = new TestMulti(CreateFloorComponents(floorZ));

View file

@ -308,49 +308,6 @@ public class ClientEnumeratorTests
}
}
[Fact]
public void ClientAtEnumerator_UsesDifferentPoint3DOverloads()
{
var map = Map.Felucca;
var location = new Point3D(800, 800, 5);
var clients = new (NetState, Mobile)[1];
try
{
clients[0] = CreateClientWithMobile(map, location);
// Test Point3D overload
var found1 = new List<NetState>();
foreach (var ns in map.GetClientsAt(location))
{
found1.Add(ns);
}
// Test (int, int) overload - should find the same client (Z is ignored)
var found2 = new List<NetState>();
foreach (var ns in map.GetClientsAt(location.X, location.Y))
{
found2.Add(ns);
}
// Test Point2D overload
var found3 = new List<NetState>();
foreach (var ns in map.GetClientsAt(new Point2D(location.X, location.Y)))
{
found3.Add(ns);
}
Assert.Single(found1);
Assert.Equal(clients[0].Item1, found1[0]);
Assert.Equal(found1, found2);
Assert.Equal(found1, found3);
}
finally
{
DeleteAll(clients);
}
}
[Fact]
public void ClientEnumerator_GetClientsInRange()
{
@ -383,39 +340,6 @@ public class ClientEnumeratorTests
}
}
[Fact]
public void ClientEnumerator_DeletedMobilesAreSkipped()
{
var map = Map.Felucca;
var rect = new Rectangle2D(1000, 1000, 16, 16);
var clients = new (NetState, Mobile)[3];
try
{
clients[0] = CreateClientWithMobile(map, new Point3D(1005, 1005, 0));
clients[1] = CreateClientWithMobile(map, new Point3D(1006, 1005, 0));
clients[2] = CreateClientWithMobile(map, new Point3D(1007, 1005, 0));
// Delete the mobile (but not the NetState)
clients[1].Item2.Delete();
var found = new List<NetState>();
foreach (var ns in map.GetClientsInBounds(rect))
{
found.Add(ns);
}
// Should skip the client whose mobile was deleted
Assert.Equal(2, found.Count);
Assert.Contains(clients[0].Item1, found);
Assert.Contains(clients[2].Item1, found);
}
finally
{
DeleteAll(clients);
}
}
private static (NetState, Mobile) CreateClientWithMobile(Map map, Point3D location)
{
// Create test NetState with real socket and buffers

View file

@ -38,100 +38,6 @@ public class ItemByDistanceEnumeratorTests
}
}
[Fact]
public void ItemByDistanceEnumerator_DeletedItemsAreSkipped()
{
var map = Map.Felucca;
var center = new Point3D(200, 200, 0);
const int range = 5;
var Items = new TestItem[3];
try
{
Items[0] = CreateItem(map, new Point3D(202, 202, 0));
Items[1] = CreateItem(map, new Point3D(203, 202, 0));
Items[2] = CreateItem(map, new Point3D(204, 202, 0));
Items[1].Delete();
var found = new List<Item>();
foreach (var (Item, _) in map.GetItemsInRangeByDistance(center, range))
{
found.Add(Item);
}
Assert.Equal(2, found.Count);
Assert.Contains(Items[0], found);
Assert.Contains(Items[2], found);
Assert.DoesNotContain(Items[1], found);
}
finally
{
DeleteAll(Items);
}
}
[Fact]
public void ItemByDistanceEnumerator_ReturnsMinDistance()
{
var map = Map.Felucca;
var center = new Point3D(300, 300, 0);
const int range = 10;
var Items = new TestItem[2];
try
{
Items[0] = CreateItem(map, new Point3D(305, 305, 0));
Items[1] = CreateItem(map, new Point3D(302, 302, 0));
var foundWithDistance = new List<(Item, int)>();
foreach (var result in map.GetItemsInRangeByDistance(center, range))
{
foundWithDistance.Add(result);
}
Assert.Equal(2, foundWithDistance.Count);
// Each Item should have a non-negative min distance
Assert.All(foundWithDistance, item => Assert.True(item.Item2 >= 0));
}
finally
{
DeleteAll(Items);
}
}
[Fact]
public void ItemByDistanceEnumerator_OrderedBySector()
{
var map = Map.Felucca;
var center = new Point3D(400, 400, 0);
const int range = Map.SectorSize * 2;
var Items = new TestItem[3];
try
{
// Place Items in different sectors
Items[0] = CreateItem(map, new Point3D(center.X + 2, center.Y + 2, 0));
Items[1] = CreateItem(map, new Point3D(center.X + Map.SectorSize + 2, center.Y + 2, 0));
Items[2] = CreateItem(map, new Point3D(center.X + 2, center.Y + Map.SectorSize + 2, 0));
var found = new List<Item>();
foreach (var (Item, _) in map.GetItemsInRangeByDistance(center, range))
{
found.Add(Item);
}
Assert.Equal(3, found.Count);
Assert.Contains(Items[0], found);
Assert.Contains(Items[1], found);
Assert.Contains(Items[2], found);
}
finally
{
DeleteAll(Items);
}
}
[Fact]
public void ItemByDistanceEnumerator_MapNullYieldsEmpty()
{
@ -239,50 +145,6 @@ public class ItemByDistanceEnumeratorTests
}
}
[Fact]
public void ItemByDistanceEnumerator_UsesDifferentPointOverloads()
{
var map = Map.Felucca;
var center = new Point3D(800, 800, 5);
const int range = 5;
var Items = new TestItem[1];
try
{
Items[0] = CreateItem(map, new Point3D(802, 802, 0));
// Test Point3D overload
var found1 = new List<Item>();
foreach (var (Item, _) in map.GetItemsInRangeByDistance(center, range))
{
found1.Add(Item);
}
// Test (int, int) overload
var found2 = new List<Item>();
foreach (var (Item, _) in map.GetItemsInRangeByDistance<Item>(center.X, center.Y, range))
{
found2.Add(Item);
}
// Test Point2D overload
var found3 = new List<Item>();
foreach (var (Item, _) in map.GetItemsInRangeByDistance(new Point2D(center.X, center.Y), range))
{
found3.Add(Item);
}
Assert.Single(found1);
Assert.Equal(Items[0], found1[0]);
Assert.Equal(found1, found2);
Assert.Equal(found1, found3);
}
finally
{
DeleteAll(Items);
}
}
[Fact]
public void ItemByDistanceEnumerator_RingTraversal()
{
@ -384,58 +246,6 @@ public class ItemByDistanceEnumeratorTests
}
}
[Fact]
public void ItemByDistanceEnumerator_MultipleRings()
{
var map = Map.Felucca;
var center = new Point3D(1100, 1100, 0);
const int range = Map.SectorSize * 3;
var Items = new List<TestItem>();
try
{
// Create a grid of Items across multiple sectors
for (var ringOffset = 0; ringOffset <= 2; ringOffset++)
{
for (var side = 0; side < 4; side++)
{
var offset = ringOffset * Map.SectorSize;
var pos = side switch
{
0 => new Point3D(center.X + offset, center.Y, 0),
1 => new Point3D(center.X, center.Y + offset, 0),
2 => new Point3D(center.X - offset, center.Y, 0),
_ => new Point3D(center.X, center.Y - offset, 0)
};
Items.Add(CreateItem(map, pos));
}
}
var found = new List<Item>();
foreach (var (Item, _) in map.GetItemsInRangeByDistance(center, range))
{
found.Add(Item);
}
// Should find all Items within range
Assert.True(found.Count > 0);
Assert.All(found, Item =>
{
var dx = Item.X - center.X;
var dy = Item.Y - center.Y;
var distSq = dx * dx + dy * dy;
Assert.True(distSq <= range * range);
});
}
finally
{
foreach (var Item in Items)
{
Item?.Delete();
}
}
}
[Fact]
public void ItemByDistanceEnumerator_MatchesBoundsEnumeratorAtMapEdge()
{
@ -556,33 +366,6 @@ public class ItemByDistanceEnumeratorTests
}
}
[Fact]
public void ItemByDistanceEnumerator_Bounds_ReturnsMinDistance()
{
var map = Map.Felucca;
var bounds = new Rectangle2D(300, 300, 20, 20);
var Items = new TestItem[2];
try
{
Items[0] = CreateItem(map, new Point3D(305, 305, 0));
Items[1] = CreateItem(map, new Point3D(315, 315, 0));
var foundWithDistance = new List<(Item, int)>();
foreach (var result in map.GetItemsInBoundsByDistance<Item>(bounds))
{
foundWithDistance.Add(result);
}
Assert.Equal(2, foundWithDistance.Count);
Assert.All(foundWithDistance, item => Assert.True(item.Item2 >= 0));
}
finally
{
DeleteAll(Items);
}
}
[Fact]
public void ItemByDistanceEnumerator_Bounds_OrdersByProximityToCenter()
{

View file

@ -256,35 +256,6 @@ public class ItemEnumeratorTests
}
}
[Fact]
public void ItemAtEnumerator_DeletedItemsAreSkipped()
{
var map = Map.Felucca;
var location = new Point3D(650, 650, 0);
var items = new Item[3];
try
{
items[0] = CreateItem(map, location);
items[1] = CreateItem(map, location);
items[2] = CreateItem(map, location);
items[1].Delete();
var found = new List<Item>();
foreach (var item in map.GetItemsAt<Item>(location))
{
found.Add(item);
}
Assert.Equal(new[] { items[0], items[2] }, found);
}
finally
{
DeleteAll(items);
}
}
[Fact]
public void ItemAtEnumerator_ItemsWithParentAreSkipped()
{
@ -365,49 +336,6 @@ public class ItemEnumeratorTests
}
}
[Fact]
public void ItemAtEnumerator_UsesDifferentPoint3DOverloads()
{
var map = Map.Felucca;
var location = new Point3D(800, 800, 5);
var items = new Item[1];
try
{
items[0] = CreateItem(map, location);
// Test Point3D overload
var found1 = new List<Item>();
foreach (var item in map.GetItemsAt(location))
{
found1.Add(item);
}
// Test (int, int) overload - should find the same item (Z is ignored)
var found2 = new List<Item>();
foreach (var item in map.GetItemsAt(location.X, location.Y))
{
found2.Add(item);
}
// Test Point2D overload
var found3 = new List<Item>();
foreach (var item in map.GetItemsAt(new Point2D(location.X, location.Y)))
{
found3.Add(item);
}
Assert.Single(found1);
Assert.Equal(items[0], found1[0]);
Assert.Equal(found1, found2);
Assert.Equal(found1, found3);
}
finally
{
DeleteAll(items);
}
}
[Fact]
public void ItemEnumerator_ZeroRangeReturnsOnlyCenter()
{

View file

@ -16,15 +16,6 @@ namespace Server.Tests.Maps
Assert.Equal(expected, flags.ToCommaDelimitedString());
}
[Fact]
public void TestFormatFeluccaOnlyMapSelection()
{
const MapSelectionFlags flags = MapSelectionFlags.Felucca;
// When, Then
Assert.Equal("Felucca", flags.ToCommaDelimitedString());
}
public class TestMapConfig
{
[JsonConverter(typeof(FlagsConverter<MapSelectionFlags>))]

View file

@ -71,76 +71,6 @@ public class MobileByDistanceEnumeratorTests
}
}
[Fact]
public void MobileByDistanceEnumerator_ReturnsMinDistance()
{
var map = Map.Felucca;
var center = new Point3D(300, 300, 0);
const int range = 10;
var mobiles = new TestMobile[2];
try
{
mobiles[0] = CreateMobile(map, new Point3D(305, 305, 0));
mobiles[1] = CreateMobile(map, new Point3D(302, 302, 0));
var foundWithDistance = new List<(Mobile, int)>();
foreach (var result in map.GetMobilesInRangeByDistance(center, range))
{
foundWithDistance.Add(result);
}
Assert.Equal(2, foundWithDistance.Count);
// Each mobile should have a non-negative min distance
Assert.All(foundWithDistance, item => Assert.True(item.Item2 >= 0));
}
finally
{
DeleteAll(mobiles);
}
}
[Fact]
public void MobileByDistanceEnumerator_OrderedBySector()
{
var map = Map.Felucca;
var center = new Point3D(400, 400, 0);
const int range = Map.SectorSize * 2;
var mobiles = new TestMobile[3];
try
{
// Place mobiles in different sectors
mobiles[0] = CreateMobile(map, new Point3D(center.X + 2, center.Y + 2, 0));
mobiles[1] = CreateMobile(map, new Point3D(center.X + Map.SectorSize + 2, center.Y + 2, 0));
mobiles[2] = CreateMobile(map, new Point3D(center.X + 2, center.Y + Map.SectorSize + 2, 0));
var found = new List<Mobile>();
foreach (var (mobile, _) in map.GetMobilesInRangeByDistance(center, range))
{
found.Add(mobile);
}
Assert.Equal(3, found.Count);
Assert.Contains(mobiles[0], found);
Assert.Contains(mobiles[1], found);
Assert.Contains(mobiles[2], found);
}
finally
{
DeleteAll(mobiles);
}
}
[Fact]
public void MobileByDistanceEnumerator_MapNullYieldsEmpty()
{
var center = new Point2D(0, 0);
var bounds = new Rectangle2D(center.m_X - 10, center.m_Y - 10, 21, 21);
var enumerator = new Map.MobileDistanceEnumerable<Mobile>(null, bounds, center, false).GetEnumerator();
Assert.False(enumerator.MoveNext());
}
[Fact]
public void MobileByDistanceEnumerator_ThrowsOnVersionChange()
{
@ -241,50 +171,6 @@ public class MobileByDistanceEnumeratorTests
}
}
[Fact]
public void MobileByDistanceEnumerator_UsesDifferentPointOverloads()
{
var map = Map.Felucca;
var center = new Point3D(800, 800, 5);
const int range = 5;
var mobiles = new TestMobile[1];
try
{
mobiles[0] = CreateMobile(map, new Point3D(802, 802, 0));
// Test Point3D overload
var found1 = new List<Mobile>();
foreach (var (mobile, _) in map.GetMobilesInRangeByDistance(center, range))
{
found1.Add(mobile);
}
// Test (int, int) overload
var found2 = new List<Mobile>();
foreach (var (mobile, _) in map.GetMobilesInRangeByDistance<Mobile>(center.X, center.Y, range))
{
found2.Add(mobile);
}
// Test Point2D overload
var found3 = new List<Mobile>();
foreach (var (mobile, _) in map.GetMobilesInRangeByDistance(new Point2D(center.X, center.Y), range))
{
found3.Add(mobile);
}
Assert.Single(found1);
Assert.Equal(mobiles[0], found1[0]);
Assert.Equal(found1, found2);
Assert.Equal(found1, found3);
}
finally
{
DeleteAll(mobiles);
}
}
[Fact]
public void MobileByDistanceEnumerator_RingTraversal()
{
@ -386,58 +272,6 @@ public class MobileByDistanceEnumeratorTests
}
}
[Fact]
public void MobileByDistanceEnumerator_MultipleRings()
{
var map = Map.Felucca;
var center = new Point3D(1100, 1100, 0);
const int range = Map.SectorSize * 3;
var mobiles = new List<TestMobile>();
try
{
// Create a grid of mobiles across multiple sectors
for (var ringOffset = 0; ringOffset <= 2; ringOffset++)
{
for (var side = 0; side < 4; side++)
{
var offset = ringOffset * Map.SectorSize;
var pos = side switch
{
0 => new Point3D(center.X + offset, center.Y, 0),
1 => new Point3D(center.X, center.Y + offset, 0),
2 => new Point3D(center.X - offset, center.Y, 0),
_ => new Point3D(center.X, center.Y - offset, 0)
};
mobiles.Add(CreateMobile(map, pos));
}
}
var found = new List<Mobile>();
foreach (var (mobile, _) in map.GetMobilesInRangeByDistance(center, range))
{
found.Add(mobile);
}
// Should find all mobiles within range
Assert.True(found.Count > 0);
Assert.All(found, mobile =>
{
var dx = mobile.X - center.X;
var dy = mobile.Y - center.Y;
var distSq = dx * dx + dy * dy;
Assert.True(distSq <= range * range);
});
}
finally
{
foreach (var mobile in mobiles)
{
mobile?.Delete();
}
}
}
private static TestMobile CreateMobile(Map map, Point3D location)
{
var mobile = new TestMobile(World.NewMobile);
@ -526,33 +360,6 @@ public class MobileByDistanceEnumeratorTests
}
}
[Fact]
public void MobileByDistanceEnumerator_Bounds_ReturnsMinDistance()
{
var map = Map.Felucca;
var bounds = new Rectangle2D(300, 300, 20, 20);
var mobiles = new TestMobile[2];
try
{
mobiles[0] = CreateMobile(map, new Point3D(305, 305, 0));
mobiles[1] = CreateMobile(map, new Point3D(315, 315, 0));
var foundWithDistance = new List<(Mobile, int)>();
foreach (var result in map.GetMobilesInBoundsByDistance<Mobile>(bounds))
{
foundWithDistance.Add(result);
}
Assert.Equal(2, foundWithDistance.Count);
Assert.All(foundWithDistance, item => Assert.True(item.Item2 >= 0));
}
finally
{
DeleteAll(mobiles);
}
}
[Fact]
public void MobileByDistanceEnumerator_Bounds_OrdersByProximityToCenter()
{

View file

@ -87,13 +87,6 @@ public class MobileEnumeratorTests
}
}
[Fact]
public void MobileEnumerator_MapNullYieldsEmpty()
{
var enumerator = new Map.MobileEnumerator<Mobile>(null, Rectangle2D.Empty, false);
Assert.False(enumerator.MoveNext());
}
[Fact]
public void MobileEnumerator_ThrowsOnVersionChange()
{

View file

@ -88,13 +88,6 @@ public class MultiEnumeratorTests
}
}
[Fact]
public void MultiEnumerator_MapNullYieldsEmpty()
{
var enumerator = new Map.MultiBoundsEnumerable<BaseMulti>(null, Rectangle2D.Empty, false).GetEnumerator();
Assert.False(enumerator.MoveNext());
}
[Fact]
public void MultiEnumerator_ThrowsOnVersionChange()
{
@ -250,35 +243,6 @@ public class MultiEnumeratorTests
}
}
[Fact]
public void MultiSectorEnumerator_DeletedMultisAreSkipped()
{
var map = Map.Felucca;
var location = new Point3D(750, 750, 0);
var multis = new TestMulti[3];
try
{
multis[0] = CreateMulti(map, location);
multis[1] = CreateMulti(map, new Point3D(location.X + 1, location.Y, 0));
multis[2] = CreateMulti(map, new Point3D(location.X + 2, location.Y, 0));
multis[1].Delete();
var found = new List<BaseMulti>();
foreach (var multi in map.GetMultisInRange<BaseMulti>(location, 10))
{
found.Add(multi);
}
Assert.Equal(new[] { multis[0], multis[2] }, found);
}
finally
{
DeleteAll(multis);
}
}
[Fact]
public void MultiSectorEnumerator_MapNullYieldsEmpty()
{
@ -286,49 +250,6 @@ public class MultiEnumeratorTests
Assert.False(enumerator.MoveNext());
}
[Fact]
public void MultiSectorEnumerator_UsesDifferentPointOverloads()
{
var map = Map.Felucca;
var location = new Point3D(800, 800, 5);
var multis = new TestMulti[1];
try
{
multis[0] = CreateMulti(map, location);
// Test Point3D overload
var found1 = new List<BaseMulti>();
foreach (var multi in map.GetMultisInSector(location))
{
found1.Add(multi);
}
// Test (int, int) overload
var found2 = new List<BaseMulti>();
foreach (var multi in map.GetMultisInSector(location.X, location.Y))
{
found2.Add(multi);
}
// Test Point2D overload
var found3 = new List<BaseMulti>();
foreach (var multi in map.GetMultisInSector(new Point2D(location.X, location.Y)))
{
found3.Add(multi);
}
Assert.Single(found1);
Assert.Equal(multis[0], found1[0]);
Assert.Equal(found1, found2);
Assert.Equal(found1, found3);
}
finally
{
DeleteAll(multis);
}
}
[Fact]
public void MultiEnumerator_ZeroRangeReturnsOnlyCenter()
{

View file

@ -7,29 +7,6 @@ namespace Server.Tests.Maps;
[Collection("Sequential Server Tests")]
public class StaticTileEnumeratorTests
{
[Fact]
public void StaticTileEnumerator_MapNullYieldsEmpty()
{
var enumerator = new Map.StaticTileEnumerable(null, new Point2D(0, 0)).GetEnumerator();
Assert.False(enumerator.MoveNext());
}
[Fact]
public void StaticTileEnumerator_EmptyLocationYieldsEmpty()
{
var map = Map.Felucca;
var location = new Point2D(100, 100);
var tiles = new List<StaticTile>();
foreach (var tile in new Map.StaticTileEnumerable(map, location, includeStatics: true, includeMultis: false))
{
tiles.Add(tile);
}
// Since we don't have actual map files loaded, this should be empty
Assert.Empty(tiles);
}
[Fact]
public void StaticTileEnumerator_IncludeStaticsOnlyWorks()
{
@ -144,24 +121,6 @@ public class StaticTileEnumeratorTests
}
}
[Fact]
public void StaticTileEnumerator_TileMatrixGetStaticTilesWorks()
{
var map = Map.Felucca;
var x = 600;
var y = 600;
// Test the TileMatrix.GetStaticTiles method
var tiles = new List<StaticTile>();
foreach (var tile in map.Tiles.GetStaticTiles(x, y))
{
tiles.Add(tile);
}
// Without map files loaded, should be empty
Assert.Empty(tiles);
}
[Fact]
public void StaticTileEnumerator_TileMatrixGetStaticAndMultiTilesWorks()
{
@ -252,13 +211,6 @@ public class StaticTileEnumeratorTests
}
}
[Fact]
public void StaticTileEnumerator_EmptyReturnsCorrectly()
{
var enumerator = Map.StaticTileEnumerable.Empty.GetEnumerator();
Assert.False(enumerator.MoveNext());
}
[Fact]
public void StaticTileEnumerator_DeletedMultiSkipped()
{

View file

@ -32,13 +32,4 @@ public class BanConfigurationTests
Assert.Equal(original.ReportRateLimitTrips, restored.ReportRateLimitTrips);
Assert.Equal(original.AutoBanDuration, restored.AutoBanDuration); // TimeSpan survives
}
[Fact]
public void BanSettings_Defaults_AreReportRateLimitTripsFourHourAutoBan()
{
var settings = new BanSettings();
Assert.True(settings.ReportRateLimitTrips);
Assert.Equal(TimeSpan.FromHours(4), settings.AutoBanDuration);
}
}

View file

@ -10,14 +10,6 @@ namespace Server.Tests;
[Collection("Sequential Server Tests")]
public class EventLoopIdleTests
{
[Fact]
public void FreshContextIsEmpty()
{
var context = new EventLoopContext();
Assert.True(context.IsEmpty);
}
[Fact]
public void PostedWorkMakesContextNonEmpty()
{

View file

@ -74,254 +74,6 @@ public class MovementThrottleLatencyTests
}
}
/// <summary>
/// Scenario: Player experiences a 3-second network lag spike, then all buffered
/// packets arrive at once. This should NOT trigger false positive detection.
///
/// Timeline:
/// - T=0-1000ms: Normal movement (10 packets at 100ms intervals)
/// - T=1000-4000ms: Network lag (client buffers packets)
/// - T=4000ms: Network recovers, 30 buffered packets arrive simultaneously
/// </summary>
[Fact]
public void LagSpike_BurstRecovery_NoFalsePositive()
{
var ns = PacketTestUtilities.CreateTestNetState();
SetTickCount(0);
// Phase 1: Normal movement for 1 second
var movements = new (int delayMs, int costMs)[10];
for (var i = 0; i < 10; i++)
{
movements[i] = (100, 100); // Normal mounted running
}
SimulateMovements(ns, movements);
// Verify normal rate before lag
var rateBeforeLag = MovementThrottle.CalculateMovementRate(ns, out var samples);
Assert.True(samples >= 8, $"Expected >= 8 samples, got {samples}");
Assert.True(rateBeforeLag >= 0.95f && rateBeforeLag <= 1.05f,
$"Expected normal rate before lag, got {rateBeforeLag}");
// Phase 2: Simulate 3 second lag followed by burst
// The gap (3000ms) is tracked, then burst packets arrive
var currentTime = Core.TickCount;
// Record the lag gap
SetTickCount(currentTime + 3000);
ns._lastGapDuration = 3000; // Gap > maxChainGap triggers this
// Burst packets arriving (5 packets with tiny intervals)
for (var i = 0; i < 5; i++)
{
SetTickCount(Core.TickCount + 2); // 2ms between packets (simultaneous arrival)
if (ns._lastMovementRecordTime > 0)
{
var interval = (int)(Core.TickCount - ns._lastMovementRecordTime);
ns._movementHistory[ns._movementHistoryIndex] = new NetState.MovementRecord
{
Interval = (short)interval,
TargetSpeed = 100,
QueueDepth = 0,
Flags = 0
};
ns._movementHistoryIndex++;
if (ns._movementHistoryIndex >= 20)
{
ns._movementHistoryIndex = 0;
ns._movementHistoryFull = true;
}
}
ns._lastMovementRecordTime = Core.TickCount;
}
// Analyze - should apply burst forgiveness due to large gap
var verdict = MovementThrottle.AnalyzeMovement(ns, out var rate, out _, out var confidence);
// The burst will have high rate due to tiny intervals, but:
// 1. _lastGapDuration > 1000 (maxChainGap/2) triggers burst forgiveness
// 2. Confidence should be reduced by 0.3f multiplier
// 3. Should NOT be Definite unless truly extreme
Assert.True(
verdict != MovementThrottle.DetectionVerdict.Definite,
$"Lag recovery should not be Definite. Rate={rate}, Confidence={confidence}, Verdict={verdict}"
);
// Gap duration should be cleared after analysis
Assert.Equal(0, ns._lastGapDuration);
}
/// <summary>
/// Scenario: Player with consistently high latency (400ms RTT) moves normally.
/// Their connection is consistent but slow - should NOT be considered "stable"
/// for the purpose of tightening detection thresholds.
/// </summary>
[Fact]
public void HighLatencyConsistent_NotConsideredStable()
{
var ns = PacketTestUtilities.CreateTestNetState();
// Set up high but consistent RTT
ns._rttHistory = new long[] { 400, 405, 398, 402, 401, 399, 403, 400 };
ns._rttSampleCount = 8;
// Calculate variance (should be low since values are consistent)
long sum = 0, sumSq = 0;
for (var i = 0; i < 8; i++)
{
sum += ns._rttHistory[i];
sumSq += ns._rttHistory[i] * ns._rttHistory[i];
}
var mean = sum / 8;
ns._rttVariance = sumSq / 8 - mean * mean;
// Variance is low (consistent), but should NOT be "stable" due to high latency
Assert.False(ns.HasStableConnection,
$"400ms RTT should not be stable. AvgRTT={ns.AverageRtt}, Variance={ns._rttVariance}");
// Now verify detection gives more leniency for high-latency connections
SimulateMovements(ns,
(105, 100), (105, 100), (105, 100), (105, 100), (105, 100),
(105, 100), (105, 100), (105, 100), (105, 100), (105, 100)
);
var verdict = MovementThrottle.AnalyzeMovement(ns, out var rate, out _, out var confidence);
// Slightly slow but high-latency player should get benefit of doubt
Assert.True(
verdict == MovementThrottle.DetectionVerdict.Normal ||
verdict == MovementThrottle.DetectionVerdict.Possible,
$"High latency player should get leniency. Verdict={verdict}"
);
}
/// <summary>
/// Scenario: RTT probing and measurement.
/// Verifies that RTT variance is calculated correctly and stability
/// is properly determined.
/// </summary>
[Fact]
public void RttMeasurement_VarianceCalculation()
{
var ns = PacketTestUtilities.CreateTestNetState();
SetTickCount(1000);
// Simulate stable low-latency RTT measurements
var rttValues = new long[] { 50, 48, 52, 49, 51, 50, 48, 52 };
ns._rttHistory = new long[8];
for (var i = 0; i < rttValues.Length; i++)
{
ns._rttHistory[i] = rttValues[i];
}
ns._rttSampleCount = 8;
// Calculate expected variance
long sum = 0, sumSq = 0;
foreach (var rtt in rttValues)
{
sum += rtt;
sumSq += rtt * rtt;
}
var expectedMean = sum / 8;
var expectedVariance = sumSq / 8 - expectedMean * expectedMean;
// Clamp negative variance (safety check)
if (expectedVariance < 0)
{
expectedVariance = 0;
}
ns._rttVariance = expectedVariance;
Assert.True(ns._rttVariance >= 0, "Variance should never be negative");
Assert.True(ns._rttVariance < 2500, "Low-variance samples should have variance < 2500");
Assert.True(ns.AverageRtt > 0 && ns.AverageRtt < 200, "Average RTT should be ~50ms");
Assert.True(ns.HasStableConnection, "Should be considered stable connection");
}
/// <summary>
/// Scenario: Actual speed hacker with stable connection.
/// Should be detected with high confidence.
/// </summary>
[Fact]
public void SpeedHack_StableConnection_DetectedDefinite()
{
var ns = PacketTestUtilities.CreateTestNetState();
// Set up stable, low-latency RTT
ns._rttHistory = new long[] { 30, 32, 28, 31, 29, 30, 31, 30 };
ns._rttSampleCount = 8;
long sum = 0, sumSq = 0;
for (var i = 0; i < 8; i++)
{
sum += ns._rttHistory[i];
sumSq += ns._rttHistory[i] * ns._rttHistory[i];
}
ns._rttVariance = sumSq / 8 - (sum / 8) * (sum / 8);
if (ns._rttVariance < 0)
{
ns._rttVariance = 0;
}
Assert.True(ns.HasStableConnection, "Should be stable for this test");
// Simulate speed hack: 50% faster movement
SimulateMovements(ns,
(67, 100), (67, 100), (67, 100), (67, 100), (67, 100),
(67, 100), (67, 100), (67, 100), (67, 100), (67, 100),
(67, 100), (67, 100), (67, 100), (67, 100), (67, 100)
);
var verdict = MovementThrottle.AnalyzeMovement(ns, out var rate, out _, out var confidence);
Assert.True(rate > 1.4f, $"Expected rate > 1.4 (50% speed hack), got {rate}");
Assert.Equal(MovementThrottle.DetectionVerdict.Definite, verdict);
Assert.True(confidence > 0.5f, $"Expected high confidence, got {confidence}");
}
/// <summary>
/// Scenario: Borderline speed hack (7% faster) with unstable connection.
/// Should give benefit of doubt (not Definite).
/// </summary>
[Fact]
public void BorderlineSpeed_UnstableConnection_BenefitOfDoubt()
{
var ns = PacketTestUtilities.CreateTestNetState();
// Set up unstable connection
ns._rttHistory = new long[] { 50, 300, 80, 250, 100, 400, 60, 350 };
ns._rttSampleCount = 8;
long sum = 0, sumSq = 0;
for (var i = 0; i < 8; i++)
{
sum += ns._rttHistory[i];
sumSq += ns._rttHistory[i] * ns._rttHistory[i];
}
ns._rttVariance = sumSq / 8 - (sum / 8) * (sum / 8);
Assert.False(ns.HasStableConnection, "Should be unstable due to high variance");
// Simulate borderline speed (7% faster)
SimulateMovements(ns,
(93, 100), (93, 100), (93, 100), (93, 100), (93, 100),
(93, 100), (93, 100), (93, 100), (93, 100), (93, 100)
);
var verdict = MovementThrottle.AnalyzeMovement(ns, out var rate, out _, out var confidence);
Assert.True(rate > 1.05f && rate < 1.10f, $"Expected rate ~1.07, got {rate}");
// Unstable connection + borderline rate = should NOT be Definite
Assert.True(
verdict != MovementThrottle.DetectionVerdict.Definite,
$"Borderline case with unstable connection should not be Definite. Verdict={verdict}, Confidence={confidence}"
);
}
/// <summary>
/// Scenario: Verifies that burst forgiveness triggers correctly and clears gap duration.
/// </summary>
@ -383,48 +135,4 @@ public class MovementThrottleLatencyTests
$"Should not be Definite after burst forgiveness. Verdict={verdict}"
);
}
/// <summary>
/// Scenario: Walking vs running speed differences.
/// Walking (200ms interval) vs mounted running (100ms interval) should both
/// calculate correct rates.
/// </summary>
[Fact]
public void WalkingVsRunning_CorrectRateCalculation()
{
// Test walking (200ms expected interval)
var nsWalking = PacketTestUtilities.CreateTestNetState();
SimulateMovements(nsWalking,
(200, 200), (200, 200), (200, 200), (200, 200), (200, 200),
(200, 200), (200, 200), (200, 200), (200, 200), (200, 200)
);
var walkingRate = MovementThrottle.CalculateMovementRate(nsWalking, out var walkingSamples);
Assert.True(walkingSamples >= 8, "Should have enough walking samples");
Assert.True(walkingRate >= 0.95f && walkingRate <= 1.05f,
$"Walking rate should be ~1.0, got {walkingRate}");
// Test mounted running (100ms expected interval)
var nsRunning = PacketTestUtilities.CreateTestNetState();
SimulateMovements(nsRunning,
(100, 100), (100, 100), (100, 100), (100, 100), (100, 100),
(100, 100), (100, 100), (100, 100), (100, 100), (100, 100)
);
var runningRate = MovementThrottle.CalculateMovementRate(nsRunning, out var runningSamples);
Assert.True(runningSamples >= 8, "Should have enough running samples");
Assert.True(runningRate >= 0.95f && runningRate <= 1.05f,
$"Running rate should be ~1.0, got {runningRate}");
// Test speed hack while walking
var nsWalkingHack = PacketTestUtilities.CreateTestNetState();
SimulateMovements(nsWalkingHack,
(100, 200), (100, 200), (100, 200), (100, 200), (100, 200),
(100, 200), (100, 200), (100, 200), (100, 200), (100, 200)
);
var walkingHackRate = MovementThrottle.CalculateMovementRate(nsWalkingHack, out _);
Assert.True(walkingHackRate >= 1.9f && walkingHackRate <= 2.1f,
$"Walking at running speed should be rate ~2.0, got {walkingHackRate}");
}
}

View file

@ -212,30 +212,6 @@ public class MovementThrottleTests
Assert.True(rate > 1.25f, $"Expected rate > 1.25, got {rate}");
}
[Fact]
public void AnalyzeMovement_ModerateSpeedHack_ReturnsPossibleOrLikely()
{
// 8% faster than expected - moderate, should be Possible or Likely
var ns = CreateNetStateWithHistory(
(93, 100), (93, 100), (93, 100), (93, 100), (93, 100),
(93, 100), (93, 100), (93, 100), (93, 100), (93, 100)
);
// Set up stable, low-latency RTT (required for Likely verdict path)
ns._rttHistory = [50, 52, 48, 51, 49, 50, 51, 50];
ns._rttSampleCount = 8;
ns._rttVariance = 4; // Low variance
var verdict = MovementThrottle.AnalyzeMovement(ns, out var rate, out var sampleCount, out var confidence);
Assert.True(
verdict == MovementThrottle.DetectionVerdict.Possible ||
verdict == MovementThrottle.DetectionVerdict.Likely,
$"Expected Possible or Likely, got {verdict}. Rate={rate}, Confidence={confidence}"
);
Assert.True(rate > 1.05f && rate < 1.15f, $"Expected rate ~1.07, got {rate}");
}
[Fact]
public void AnalyzeMovement_BurstAfterLargeGap_ReducesConfidence()
{
@ -279,25 +255,6 @@ public class MovementThrottleTests
Assert.Equal(MovementThrottle.DetectionVerdict.Definite, verdict);
}
[Fact]
public void AnalyzeMovement_StableConnectionLowLatency_IncreasesConfidence()
{
var ns = CreateNetStateWithHistory(
(95, 100), (95, 100), (95, 100), (95, 100), (95, 100),
(95, 100), (95, 100), (95, 100), (95, 100), (95, 100)
);
// Set up stable, low-latency RTT
ns._rttHistory = [50, 52, 48, 51, 49, 50, 51, 50];
ns._rttSampleCount = 8;
ns._rttVariance = 4; // Low variance
var verdict = MovementThrottle.AnalyzeMovement(ns, out var rate, out var sampleCount, out var confidence);
// With stable connection, even small anomalies are more suspicious
Assert.True(confidence > 0.2f, $"Expected higher confidence for stable connection, got {confidence}");
}
[Fact]
public void AnalyzeMovement_UnstableConnection_ReducesConfidence()
{

View file

@ -390,38 +390,6 @@ public class NetStateSendBufferTests
}
}
[Fact]
public void Send_WithPartialRemainder_GrowsAndCopiesFullPayload()
{
var ns = CreateAuthenticatedNetState(out var client);
var baseSize = ns._socket.SendBuffer.PhysicalSize;
try
{
var first = Pattern(baseSize / 2, 200);
var second = Pattern(baseSize / 2 + 1, 201);
var expected = new byte[first.Length + second.Length];
first.CopyTo(expected, 0);
second.CopyTo(expected, first.Length);
// Remainder too small for the second send
ns.Send(first);
ns.Send(second);
Assert.True(ns.Running);
Assert.Equal(string.Empty, ns._disconnectReason);
Assert.True(ns._socket.SendBuffer.PhysicalSize > baseSize);
Assert.True(ns._sendBufferGrown);
Assert.Equal(expected, ReadAll(client, expected.Length));
}
finally
{
ns.Dispose();
client.Close();
}
}
[Fact]
public void Send_WithSendInFlight_GrowsAndStreamStaysIntact()
{
@ -628,58 +596,4 @@ public class NetStateSendBufferTests
// growth off; budget untouched
Assert.Equal(1L, NetState.CoerceSendBufferGrowthBudget(1, sendBufferSize, sendBufferSize));
}
[Fact]
public void CoerceMaxBufferSlabs_ClampsToOneSlabPerConnection()
{
var maxConnections = NetState.SocketManager.MaxSockets;
// fewer than one slab is meaningless
Assert.Equal(1, NetState.CoerceMaxBufferSlabs(0));
Assert.Equal(1, NetState.CoerceMaxBufferSlabs(-4));
// more slabs than connections cannot make a slab any smaller
Assert.Equal(maxConnections, NetState.CoerceMaxBufferSlabs(maxConnections * 2));
Assert.Equal(128, NetState.CoerceMaxBufferSlabs(128));
}
[Fact]
public void CoerceInitialBufferSlabs_ClampsToTheSlabCount()
{
var slabs = NetState.BasePoolSlabCount(128);
Assert.True(slabs > 1);
Assert.Equal(1, NetState.CoerceInitialBufferSlabs(0, slabs));
Assert.Equal(1, NetState.CoerceInitialBufferSlabs(-1, slabs));
// a pool never holds more slabs than it has
Assert.Equal(slabs, NetState.CoerceInitialBufferSlabs(slabs + 1, slabs));
Assert.Equal(4, NetState.CoerceInitialBufferSlabs(4, slabs));
}
[Fact]
public void Ring_RegisteredBufferTable_MatchesTheConfiguredSlabCount()
{
var manager = NetState.SocketManager;
var maxBufferSlabs = NetState.CoerceMaxBufferSlabs(ServerConfiguration.GetSetting("network.maxBufferSlabs", NetState.DefaultMaxBufferSlabs));
// A table smaller than this throws at manager construction. Connections start on the
// transport minimum until the game server promotes them, so the formula must count those too.
// Mirrors what production passes: the request, not the effective size the manager may coerce
// down to (RequiredRegisteredBuffers treats a non-zero request as a pool either way).
Assert.Equal(
RingSocketManager.RequiredRegisteredBuffers(
manager.MaxSockets,
NetState.SendBufferSize,
manager.MaxSendBufferSize,
manager.SendBufferGrowthBudget,
maxBufferSlabs,
IORingBuffer.MinimumSize,
IORingBuffer.MinimumSize
),
NetState.Ring.MaxRegisteredBuffers
);
}
}

View file

@ -42,20 +42,6 @@ public class NetworkCompressionBoundsTests
}
}
[Fact]
public void StillCompressesWhenOutputIsLargeEnough()
{
var input = new byte[64];
Array.Fill(input, (byte)'A');
var output = new byte[256];
var written = NetworkCompression.Compress(input, output);
Assert.True(written > 0);
Assert.True(written <= output.Length);
}
[Fact]
public void ReportsFailureRatherThanOverrunningATightOutput()
{

View file

@ -1,80 +0,0 @@
using System.Net.Sockets;
using Server.Network;
using Xunit;
namespace Server.Tests.Network;
[Collection("Sequential Server Tests")]
public class NetworkStatsTests
{
private static byte[] Pattern(int length, int seed)
{
var data = new byte[length];
new System.Random(seed).NextBytes(data);
return data;
}
[Fact]
public void MaintainSendBuffers_RecordsTheRefusalsItResets()
{
var previous = NetState._availableMemoryBytes;
NetState ns = null;
Socket client = null;
try
{
ns = PacketTestUtilities.CreateTestNetState(out client);
ns._protocolState = NetState.ProtocolState.GameServer_AwaitingGameServerLogin;
ns.Account = new MockAccount();
var baseSize = ns._socket.SendBuffer.PhysicalSize;
NetState.MaintainSendBuffers(); // drain whatever earlier tests refused
NetState._availableMemoryBytes = 1; // any working set is above 80% of one byte
for (var i = 0; i < 6; i++)
{
ns.Send(Pattern(baseSize / 4, i));
}
// The sweep refreshes _availableMemoryBytes, so refusals must happen before it
NetState.MaintainSendBuffers();
var stats = NetState.GetNetworkStats();
Assert.True(stats.LastSweep.Ran);
Assert.True(stats.LastSweep.CeilingRefusals >= 1);
// Counters are per sweep, not cumulative
NetState.MaintainSendBuffers();
Assert.Equal(0, NetState.GetNetworkStats().LastSweep.CeilingRefusals);
}
finally
{
NetState._availableMemoryBytes = previous;
ns?.Dispose();
client?.Close();
}
}
[Fact]
public void GetNetworkStats_CountsAuthenticatedConnectionsSeparately()
{
var ns = PacketTestUtilities.CreateTestNetState(out var client);
try
{
var before = NetState.GetNetworkStats();
ns._protocolState = NetState.ProtocolState.GameServer_AwaitingGameServerLogin;
ns.Account = new MockAccount();
var after = NetState.GetNetworkStats();
Assert.Equal(before.Connected, after.Connected);
Assert.Equal(before.Authenticated + 1, after.Authenticated);
Assert.True(after.Connected >= after.Authenticated);
}
finally
{
ns.Dispose();
client.Close();
}
}
}

View file

@ -25,18 +25,6 @@ public class ObjectPropertyListReentrancyTests
return value;
}
[Fact]
public void InterpolatedAdd_ResetMidHole_DoesNotThrow()
{
var opl = new ObjectPropertyList(null);
var ex = Record.Exception(
() => opl.Add(1060776, $"{ResettingHole(opl, "Knight")}\t{"Council of Mages"}")
);
Assert.Null(ex);
}
[Fact]
public void InterpolatedAdd_DisposeMidHole_DoesNotThrow()
{
@ -57,19 +45,6 @@ public class ObjectPropertyListReentrancyTests
[Collection("Sequential Server Tests")]
public class ObjectPropertyListNestedBuildTests
{
[Fact]
public void NestedBuild_DoesNotUnguardTheOuterList()
{
var outer = new ObjectPropertyList(null);
var inner = new ObjectPropertyList(null);
outer.IsBuilding = true;
inner.IsBuilding = true; // another entity starts building, and finishes
inner.IsBuilding = false;
Assert.True(outer.IsBuilding);
}
[Fact]
public void Reset_MidInterpolation_LeavesTheListUsable()
{

View file

@ -1,46 +0,0 @@
using System;
using System.Buffers.Binary;
using System.Runtime.InteropServices;
using Xunit;
namespace Server.Tests;
public class DecimalSerializationTests
{
private static void Write(byte[] buffer, decimal value)
{
Span<int> bytes = stackalloc int[sizeof(decimal) / 4];
decimal.GetBits(value, bytes);
MemoryMarshal.Cast<int, byte>(bytes).CopyTo(buffer.AsSpan());
}
private static int ReadInt(ReadOnlySpan<byte> buffer) => BinaryPrimitives.ReadInt32LittleEndian(buffer);
private static decimal ReadDecimal(ReadOnlySpan<byte> buffer) => new(stackalloc int[4] { ReadInt(buffer), ReadInt(buffer[4..]), ReadInt(buffer[8..]), ReadInt(buffer[12..]) });
public static TheoryData<decimal> Data =>
new()
{
123.46m,
0.0256m,
10000m,
2m,
0.0001m,
0.0000000000000000000000000001m
};
[Theory]
[MemberData(nameof(Data))]
public void TestSerializeDecimal(decimal value)
{
// Arrange
var buffer = new byte[sizeof(decimal)];
// Act
Write(buffer, value);
// Assert
Assert.Equal(value, ReadDecimal(buffer));
}
}

View file

@ -1,21 +0,0 @@
using System;
using Xunit;
namespace Server.Tests;
public class EnumConversionTests
{
[Fact]
public void TestToEnum()
{
var e = ReadEnum<TileFlag>();
Assert.Equal(TileFlag.Container, e);
}
private static unsafe T ReadEnum<T>() where T : unmanaged, Enum
{
var num = (long)TileFlag.Container;
return *(T*)&num;
}
}

View file

@ -54,15 +54,6 @@ namespace Server.Tests
Assert.Equal("Test ModernUO", result.Name);
}
[Fact]
public void TestTwoParamsOptionalActivator()
{
var result = typeof(TestTwoOptionalParamsClass).CreateInstance<TestTwoOptionalParamsClass>(10, "Prod ModernUO");
Assert.IsType<TestTwoOptionalParamsClass>(result);
Assert.Equal(10, result.Amount);
Assert.Equal("Prod ModernUO", result.Name);
}
private class TestZeroParamsClass
{
}

View file

@ -35,54 +35,6 @@ public class HtmlEscapeTests
Assert.Equal(expected, result);
}
[Theory(DisplayName = "Multiple instances of same character")]
[InlineData("<><>", "&lt;&gt;&lt;&gt;")]
[InlineData("&&&&", "&amp;&amp;&amp;&amp;")]
[InlineData("\"\"\"", "&quot;&quot;&quot;")]
[InlineData("'''", "&#39;&#39;&#39;")]
[InlineData(">>>", "&gt;&gt;&gt;")]
public void EscapeHtml_MultipleSpecialCharacters_EscapesAll(string input, string expected)
{
var result = input.EscapeHtml();
Assert.Equal(expected, result);
}
[Theory(DisplayName = "Mixed content")]
[InlineData("Hello <world>", "Hello &lt;world&gt;")]
[InlineData("<div>Hello</div>", "&lt;div&gt;Hello&lt;/div&gt;")]
[InlineData("Tom & Jerry", "Tom &amp; Jerry")]
[InlineData("He said \"hello\"", "He said &quot;hello&quot;")]
[InlineData("It's a test", "It&#39;s a test")]
public void EscapeHtml_MixedContent_EscapesMixedSpecialCharacters(string input, string expected)
{
var result = input.EscapeHtml();
Assert.Equal(expected, result);
}
[Theory(DisplayName = "Starting with special character")]
[InlineData("<Hello", "&lt;Hello")]
[InlineData(">World", "&gt;World")]
[InlineData("&Start", "&amp;Start")]
[InlineData("\"Quote", "&quot;Quote")]
[InlineData("'Apostrophe", "&#39;Apostrophe")]
public void EscapeHtml_StartsWithSpecialCharacter_EscapesStart(string input, string expected)
{
var result = input.EscapeHtml();
Assert.Equal(expected, result);
}
[Theory(DisplayName = "Ending with special character")]
[InlineData("Hello<", "Hello&lt;")]
[InlineData("World>", "World&gt;")]
[InlineData("End&", "End&amp;")]
[InlineData("Quote\"", "Quote&quot;")]
[InlineData("Test'", "Test&#39;")]
public void EscapeHtml_EndsWithSpecialCharacter_EscapesEnd(string input, string expected)
{
var result = input.EscapeHtml();
Assert.Equal(expected, result);
}
[Theory(DisplayName = "Complex mixed scenarios")]
[InlineData("<p>Hello & goodbye</p>", "&lt;p&gt;Hello &amp; goodbye&lt;/p&gt;")]
[InlineData("&lt;already&gt;", "&amp;lt;already&amp;gt;")]
@ -103,59 +55,6 @@ public class HtmlEscapeTests
Assert.Empty(result);
}
[Fact(DisplayName = "Empty string input")]
public void EscapeHtml_EmptyString_ReturnsEmpty()
{
var result = "".EscapeHtml();
Assert.Empty(result);
}
[Fact(DisplayName = "Only special characters")]
public void EscapeHtml_OnlySpecialCharacters_EscapesAll()
{
const string input = "<>&\"'";
const string expected = "&lt;&gt;&amp;&quot;&#39;";
var result = input.EscapeHtml();
Assert.Equal(expected, result);
}
[Fact(DisplayName = "Ampersand must be escaped first")]
public void EscapeHtml_AmpersandFirst_PreventDoubleEscaping()
{
// This is critical: & must be escaped to &amp;
// If we're not careful, we could double-escape already-escaped content
const string input = "&lt;";
const string expected = "&amp;lt;";
var result = input.EscapeHtml();
Assert.Equal(expected, result);
}
[Theory(DisplayName = "ReadOnlySpan overload - no special characters")]
[InlineData("Hello World")]
[InlineData("Plain text")]
public void EscapeHtml_ReadOnlySpan_NoSpecialCharacters_ReturnsUnchanged(string input)
{
var result = input.AsSpan().EscapeHtml();
Assert.Equal(input, result);
}
[Theory(DisplayName = "ReadOnlySpan overload - with special characters")]
[InlineData("<div>", "&lt;div&gt;")]
[InlineData("Tom & Jerry", "Tom &amp; Jerry")]
public void EscapeHtml_ReadOnlySpan_WithSpecialCharacters_EscapesCorrectly(string input, string expected)
{
var result = input.AsSpan().EscapeHtml();
Assert.Equal(expected, result);
}
[Theory(DisplayName = "ReadOnlySpan overload - empty input")]
[InlineData("")]
public void EscapeHtml_ReadOnlySpan_Empty_ReturnsEmpty(string input)
{
var result = input.AsSpan().EscapeHtml();
Assert.Empty(result);
}
[Fact(DisplayName = "Consecutive special characters")]
public void EscapeHtml_ConsecutiveSpecialCharacters_EscapesAll()
{
@ -165,51 +64,6 @@ public class HtmlEscapeTests
Assert.Equal(expected, result);
}
[Fact(DisplayName = "Special characters with single normal character between")]
public void EscapeHtml_SpecialCharactersWithGaps_EscapesAll()
{
const string input = "<a>b&c\"d'e";
const string expected = "&lt;a&gt;b&amp;c&quot;d&#39;e";
var result = input.EscapeHtml();
Assert.Equal(expected, result);
}
[Fact(DisplayName = "HTML tags")]
public void EscapeHtml_HtmlTags_EscapesTagBrackets()
{
var input = "<html><body>Hello</body></html>";
var expected = "&lt;html&gt;&lt;body&gt;Hello&lt;/body&gt;&lt;/html&gt;";
var result = input.EscapeHtml();
Assert.Equal(expected, result);
}
[Fact(DisplayName = "HTML attributes with mixed quotes")]
public void EscapeHtml_HtmlAttributesWithQuotes_EscapesCorrectly()
{
var input = "<a href=\"test\" data='value'>";
var expected = "&lt;a href=&quot;test&quot; data=&#39;value&#39;&gt;";
var result = input.EscapeHtml();
Assert.Equal(expected, result);
}
[Theory(DisplayName = "Whitespace handling")]
[InlineData(" spaces ", " spaces ")]
[InlineData("\ttabs\t", "\ttabs\t")]
[InlineData("\nnewlines\n", "\nnewlines\n")]
public void EscapeHtml_Whitespace_PreservedAsIs(string input, string expected)
{
var result = input.EscapeHtml();
Assert.Equal(expected, result);
}
[Fact(DisplayName = "Performance: long string without special characters")]
public void EscapeHtml_LongStringNoSpecialCharacters_ReturnsQuickly()
{
var input = new string('a', 10000);
var result = input.EscapeHtml();
Assert.Equal(input, result);
}
[Fact(DisplayName = "Performance: long string with special characters")]
public void EscapeHtml_LongStringWithSpecialCharacters_HandlesCorrectly()
{
@ -229,15 +83,4 @@ public class HtmlEscapeTests
var result = input.EscapeHtml();
Assert.Equal(expected, result);
}
[Fact(DisplayName = "String overload matches ReadOnlySpan overload")]
public void EscapeHtml_StringVsReadOnlySpan_ProduceSameResult()
{
const string input = "<div>Tom & Jerry 'in' \"quotes\"</div>";
var resultString = input.EscapeHtml();
var resultSpan = input.AsSpan().EscapeHtml();
Assert.Equal(resultString, resultSpan);
}
}

View file

@ -7,17 +7,6 @@ namespace Server.Tests;
[Collection("Sequential Server Tests")]
public class TestStringHelpers
{
[Theory]
[InlineData(null, "default value", "default value")]
[InlineData("", "default value", "default value")]
[InlineData("this is a valid string", "default value", "this is a valid string")]
public void TestIsNullOrDefault(string value, string defaultValue, string expected)
{
var actual = value.DefaultIfNullOrEmpty(defaultValue);
Assert.Equal(expected, actual);
}
[Theory]
[InlineData("this is not capitalized", "This Is Not Capitalized")]
[InlineData("", "")]