ModernUO/Projects/UOContent.Tests/Tests/Engines/Events/RecurrencePatternTests.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

293 lines
14 KiB
C#

using System;
using Server;
using Server.Engines.Events;
using Xunit;
namespace UOContent.Tests;
public class RecurrencePatternTests
{
[Theory]
[InlineData(2024, 5, 15, 12, 30, 1, 2024, 5, 15, 13, 30)] // Normal case
[InlineData(2024, 5, 15, 23, 30, 2, 2024, 5, 16, 1, 30)] // Cross day boundary
public void HourlyRecurrencePattern_GetNextOccurrence_ReturnsCorrectTime(
int startYear, int startMonth, int startDay, int startHour, int startMinute,
int intervalHours,
int expectedYear, int expectedMonth, int expectedDay, int expectedHour, int expectedMinute)
{
// Arrange
var tz = TimeZoneInfo.Utc;
var pattern = new HourlyRecurrencePattern(intervalHours);
var afterUtc = new DateTime(startYear, startMonth, startDay, startHour, startMinute, 0, DateTimeKind.Utc);
var time = new TimeOnly(0, expectedMinute);
// Act
var result = pattern.GetNextOccurrence(afterUtc, time, tz);
// Assert
var expected = new DateTime(expectedYear, expectedMonth, expectedDay, expectedHour, expectedMinute, 0, DateTimeKind.Utc);
Assert.Equal(expected, result);
}
[Theory]
[InlineData(2024, 5, 15, 12, 30, 1, 2024, 5, 16, 12, 30)] // Next day
[InlineData(2024, 5, 31, 12, 30, 2, 2024, 6, 2, 12, 30)] // Across month boundary
public void DailyRecurrencePattern_GetNextOccurrence_ReturnsCorrectDay(
int startYear, int startMonth, int startDay, int startHour, int startMinute,
int intervalDays,
int expectedYear, int expectedMonth, int expectedDay, int expectedHour, int expectedMinute)
{
// Arrange
var tz = TimeZoneInfo.Utc;
var pattern = new DailyRecurrencePattern(intervalDays);
var afterUtc = new DateTime(startYear, startMonth, startDay, startHour, startMinute, 0, DateTimeKind.Utc);
var time = new TimeOnly(expectedHour, expectedMinute);
// Act
var result = pattern.GetNextOccurrence(afterUtc, time, tz);
// Assert
var expected = new DateTime(expectedYear, expectedMonth, expectedDay, expectedHour, expectedMinute, 0, DateTimeKind.Utc);
Assert.Equal(expected, result);
}
[Theory]
[InlineData(2024, 5, 15, 12, 30, 1, AllowedDays.All, 2024, 5, 16, 12, 30)] // Next day (Thursday)
[InlineData(2024, 5, 15, 12, 30, 1, AllowedDays.Monday, 2024, 5, 20, 12, 30)] // Next Monday
[InlineData(2024, 5, 15, 12, 30, 2, AllowedDays.Wednesday, 2024, 5, 29, 12, 30)] // Biweekly Wednesday
public void WeeklyRecurrencePattern_BasicPatterns_ReturnsCorrectDay(
int startYear, int startMonth, int startDay, int startHour, int startMinute,
int intervalWeeks, AllowedDays allowedDays,
int expectedYear, int expectedMonth, int expectedDay, int expectedHour, int expectedMinute)
{
// Arrange
var tz = TimeZoneInfo.Utc;
var pattern = new WeeklyRecurrencePattern(intervalWeeks, AllowedMonths.All, allowedDays);
var afterUtc = new DateTime(startYear, startMonth, startDay, startHour, startMinute, 0, DateTimeKind.Utc);
var time = new TimeOnly(expectedHour, expectedMinute);
// Act
var result = pattern.GetNextOccurrence(afterUtc, time, tz);
// Assert
var expected = new DateTime(expectedYear, expectedMonth, expectedDay, expectedHour, expectedMinute, 0, DateTimeKind.Utc);
Assert.Equal(expected, result);
}
[Theory]
[InlineData(2024, 12, 30, 12, 30, 1, AllowedDays.Monday, 2025, 1, 6, 12, 30)] // Across year boundary
[InlineData(2024, 5, 31, 12, 30, 1, AllowedDays.Saturday, 2024, 6, 1, 12, 30)] // Across month boundary
public void WeeklyRecurrencePattern_CrossBoundaries_ReturnsCorrectDay(
int startYear, int startMonth, int startDay, int startHour, int startMinute,
int intervalWeeks, AllowedDays allowedDays,
int expectedYear, int expectedMonth, int expectedDay, int expectedHour, int expectedMinute)
{
// Arrange
var tz = TimeZoneInfo.Utc;
var pattern = new WeeklyRecurrencePattern(intervalWeeks, AllowedMonths.All, allowedDays);
var afterUtc = new DateTime(startYear, startMonth, startDay, startHour, startMinute, 0, DateTimeKind.Utc);
var time = new TimeOnly(expectedHour, expectedMinute);
// Act
var result = pattern.GetNextOccurrence(afterUtc, time, tz);
// Assert
var expected = new DateTime(expectedYear, expectedMonth, expectedDay, expectedHour, expectedMinute, 0, DateTimeKind.Utc);
Assert.Equal(expected, result);
}
[Theory]
[InlineData(2024, 5, 15, 12, 30, AllowedMonths.January | AllowedMonths.February, AllowedDays.Wednesday, 2025, 1, 1, 12, 30)] // Skip to January
[InlineData(2024, 12, 15, 12, 30, AllowedMonths.January | AllowedMonths.December, AllowedDays.Wednesday, 2024, 12, 18, 12, 30)] // Same month
[InlineData(2024, 1, 31, 12, 30, AllowedMonths.February | AllowedMonths.April, AllowedDays.Saturday, 2024, 2, 3, 12, 30)] // Next month allowed
public void WeeklyRecurrencePattern_MonthFiltering_ReturnsCorrectDay(
int startYear, int startMonth, int startDay, int startHour, int startMinute,
AllowedMonths allowedMonths, AllowedDays allowedDays,
int expectedYear, int expectedMonth, int expectedDay, int expectedHour, int expectedMinute)
{
// Arrange
var tz = TimeZoneInfo.Utc;
var pattern = new WeeklyRecurrencePattern(1, allowedMonths, allowedDays);
var afterUtc = new DateTime(startYear, startMonth, startDay, startHour, startMinute, 0, DateTimeKind.Utc);
var time = new TimeOnly(expectedHour, expectedMinute);
// Act
var result = pattern.GetNextOccurrence(afterUtc, time, tz);
// Assert
var expected = new DateTime(expectedYear, expectedMonth, expectedDay, expectedHour, expectedMinute, 0, DateTimeKind.Utc);
Assert.Equal(expected, result);
}
[Theory]
[InlineData("America/New_York", 2024, 3, 9, 12, 0, 1, AllowedDays.Sunday, 2024, 3, 10, 12, 0)] // Before spring forward
[InlineData("America/New_York", 2024, 3, 10, 2, 30, 1, AllowedDays.Sunday, 2024, 3, 17, 2, 30)] // During spring forward (invalid time)
[InlineData("America/New_York", 2024, 11, 2, 12, 0, 1, AllowedDays.Sunday, 2024, 11, 3, 12, 0)] // Before fall back
[InlineData("America/New_York", 2024, 11, 3, 1, 30, 1, AllowedDays.Sunday, 2024, 11, 10, 1, 30)] // During fall back (ambiguous time)
public void WeeklyRecurrencePattern_DSTTransitions_HandlesCorrectly(
string tzId, int startYear, int startMonth, int startDay, int startHour, int startMinute,
int intervalWeeks, AllowedDays allowedDays,
int expectedYear, int expectedMonth, int expectedDay, int expectedHour, int expectedMinute)
{
// Arrange
var tz = TimeZoneInfo.FindSystemTimeZoneById(tzId);
var pattern = new WeeklyRecurrencePattern(intervalWeeks, AllowedMonths.All, allowedDays);
var startLocal = new DateTime(startYear, startMonth, startDay, startHour, startMinute, 0);
var afterUtc = startLocal.LocalToUtc(tz);
var time = new TimeOnly(expectedHour, expectedMinute);
// Act
var result = pattern.GetNextOccurrence(afterUtc, time, tz);
// Assert
var resultLocal = TimeZoneInfo.ConvertTimeFromUtc(result, tz);
Assert.Equal(new DateTime(expectedYear, expectedMonth, expectedDay, expectedHour, expectedMinute, 0), resultLocal);
}
[Theory]
[InlineData(2024, 5, 29, 12, 0, AllowedDays.None, 2024, 5, 29, 12, 30)] // Default to current day
[InlineData(2024, 5, 29, 12, 0, AllowedDays.Wednesday | AllowedDays.Friday, 2024, 5, 29, 12, 30)] // Current day is Wednesday
[InlineData(2024, 5, 29, 12, 0, AllowedDays.Monday | AllowedDays.Friday, 2024, 5, 31, 12, 30)] // Next allowed day (Friday)
public void WeeklyRecurrencePattern_DaysOfWeekHandling_ReturnsCorrectDay(
int startYear, int startMonth, int startDay, int startHour, int startMinute,
AllowedDays allowedDays,
int expectedYear, int expectedMonth, int expectedDay, int expectedHour, int expectedMinute)
{
// Arrange
var tz = TimeZoneInfo.Utc;
var pattern = new WeeklyRecurrencePattern(1, AllowedMonths.All, allowedDays);
var afterUtc = new DateTime(startYear, startMonth, startDay, startHour, startMinute, 0, DateTimeKind.Utc);
var time = new TimeOnly(expectedHour, expectedMinute);
// Act
var result = pattern.GetNextOccurrence(afterUtc, time, tz);
// Assert
var expected = new DateTime(expectedYear, expectedMonth, expectedDay, expectedHour, expectedMinute, 0, DateTimeKind.Utc);
Assert.Equal(expected, result);
}
[Theory]
[InlineData(2024, 1, 29, 12, 0, 1, 2024, 1, 29, 12, 30, AllowedMonths.January | AllowedMonths.March, 2024, 1, 30, 12, 30)] // Current month allowed
[InlineData(2024, 1, 31, 12, 0, 1, 2024, 1, 31, 12, 30, AllowedMonths.January | AllowedMonths.March, 2024, 3, 1, 12, 30)] // Skip February
public void WeeklyRecurrencePattern_WeekSpanningMonths_ReturnsCorrectDay(
int startYear, int startMonth, int startDay, int startHour, int startMinute,
int intervalWeeks,
int expectedYear, int expectedMonth, int expectedDay, int expectedHour, int expectedMinute,
AllowedMonths allowedMonths,
int nextExpectedYear, int nextExpectedMonth, int nextExpectedDay, int nextExpectedHour, int nextExpectedMinute)
{
// Arrange
var tz = TimeZoneInfo.Utc;
var pattern = new WeeklyRecurrencePattern(intervalWeeks, allowedMonths);
var afterUtc = new DateTime(startYear, startMonth, startDay, startHour, startMinute, 0, DateTimeKind.Utc);
var time = new TimeOnly(expectedHour, expectedMinute);
// Act - first call should hit expectedDate
var result = pattern.GetNextOccurrence(afterUtc, time, tz);
// Assert
var expected = new DateTime(expectedYear, expectedMonth, expectedDay, expectedHour, expectedMinute, 0, DateTimeKind.Utc);
Assert.Equal(expected, result);
// Call again - should move to next available day in allowed months
var nextResult = pattern.GetNextOccurrence(result, time, tz);
var nextExpected = new DateTime(nextExpectedYear, nextExpectedMonth, nextExpectedDay, nextExpectedHour, nextExpectedMinute, 0, DateTimeKind.Utc);
Assert.Equal(nextExpected, nextResult);
}
[Theory]
[InlineData(2024, 11, 3, 1, 30, DayOfWeek.Sunday, OrdinalDayOccurrence.First, "America/New_York", 2024, 11, 3)]
[InlineData(2024, 3, 31, 0, 0, DayOfWeek.Sunday, OrdinalDayOccurrence.Last, "America/New_York", 2024, 3, 31)]
[InlineData(2024, 3, 31, 0, 0, DayOfWeek.Sunday, OrdinalDayOccurrence.Fifth, "America/New_York", 2024, 3, 31)]
[InlineData(2024, 4, 3, 0, 0, DayOfWeek.Sunday, OrdinalDayOccurrence.Fifth, "America/New_York", 2024, 6, 30)] // June has 5 Sundays (2, 9, 16, 23, 30)
public void MonthlyOrdinalRecurrencePattern_GetNextOccurrence_ReturnsCorrectDate(
int startYear, int startMonth, int startDay, int startHour, int startMinute,
DayOfWeek dow, OrdinalDayOccurrence ordinal, string tzId,
int expectedYear, int expectedMonth, int expectedDay)
{
// Arrange
var tz = TimeZoneInfo.FindSystemTimeZoneById(tzId);
var pattern = new MonthlyOrdinalRecurrencePattern(ordinal, dow);
// Use a day BEFORE the expected date
var startLocal = new DateTime(startYear, startMonth, startDay, startHour, startMinute, 0);
var beforeUtc = startLocal.AddDays(-1).LocalToUtc(tz);
var timeOnly = new TimeOnly(startHour, startMinute);
// Act
var result = pattern.GetNextOccurrence(beforeUtc, timeOnly, tz);
// Assert - convert back to local for comparison
var resultLocal = TimeZoneInfo.ConvertTimeFromUtc(result, tz);
Assert.Equal(new DateTime(expectedYear, expectedMonth, expectedDay, startHour, startMinute, 0), resultLocal);
}
[Theory]
[InlineData("America/New_York", 2024, 3, 10, 2, 30)] // Spring forward - 2:30 AM doesn't exist
[InlineData("America/New_York", 2024, 11, 3, 1, 30)] // Fall back - 1:30 AM happens twice
public void MonthlyRecurrencePattern_DSTTransitions_HandlesCorrectly(
string tzId, int year, int month, int day, int hour, int minute)
{
// Arrange
var tz = TimeZoneInfo.FindSystemTimeZoneById(tzId);
var pattern = new MonthlyRecurrencePattern(day);
var localDateBefore = new DateTime(year, month, day, 0, 0, 0);
var beforeUtc = localDateBefore.LocalToUtc(tz);
var timeOnly = new TimeOnly(hour, minute);
// Act - shouldn't throw exceptions
var result = pattern.GetNextOccurrence(beforeUtc, timeOnly, tz);
// Assert - just make sure we got a valid result
Assert.NotEqual(DateTime.MaxValue, result);
// For invalid times (spring forward), should skip to next valid time
var resultLocal = TimeZoneInfo.ConvertTimeFromUtc(result, tz);
if (month == 3) // Spring forward
{
// Should skip the invalid 2:30 AM time
Assert.True(resultLocal.Hour >= 3 || resultLocal > new DateTime(year, month, day));
}
}
[Fact]
public void MonthlyRecurrence_MonthWithoutDay_SkipsToNextValidMonth()
{
// Arrange
var tz = TimeZoneInfo.Utc;
var pattern = new MonthlyRecurrencePattern(31);
// February doesn't have 31 days
var februaryDate = new DateTime(2024, 2, 1, 12, 0, 0, DateTimeKind.Utc);
var timeOnly = new TimeOnly(12, 0);
// Act
var result = pattern.GetNextOccurrence(februaryDate, timeOnly, tz);
// Assert - should be March 31
Assert.Equal(new DateTime(2024, 3, 31, 12, 0, 0), result);
}
[Theory]
[InlineData("America/New_York", 2024, 11, 3, 1, 30)] // Fall back - ambiguous time
public void LocalToUtc_AmbiguousTime_HandlesConsistently(
string tzId, int year, int month, int day, int hour, int minute)
{
// Arrange
var tz = TimeZoneInfo.FindSystemTimeZoneById(tzId);
var localTime = new DateTime(year, month, day, hour, minute, 0);
// Act
var result = localTime.LocalToUtc(tz);
// Assert - should be consistent, not testing exact value
Assert.Equal(DateTimeKind.Utc, result.Kind);
// Convert back should give either standard or DST time
var backToLocal = TimeZoneInfo.ConvertTimeFromUtc(result, tz);
Assert.Equal(new DateTime(year, month, day, hour, minute, 0), backToLocal);
}
}