Files
MeterVault/tests/Core.Tests/Analysis/BucketPlannerTests.cs
T
Florian Schmidt a08e9f781f
ci / build-test (push) Successful in 2m41s
Analysis: read a rarely-read meter as coarse, not absent; newest rows first
Three things a reported Heizoel page got wrong at once. Its tank is dipped
a few times a year and its burner read every few months, which is exactly
the shape the coverage rules had not been walked through.

"No data" for data that exists. A tank books nothing until the next
dipstick closes the interval, so the stretch after the last dipstick is
covered by no run at all, and a bucket no run covers was reported missing.
The burner, whose run reaches into the window, said "only coarser data" --
the honest answer -- so one card claimed there was nothing while the
coverage panel beside it listed years of data. A bucket that no run covers,
no gap overlaps and no opening balance explains now reports the meter's
resolution when its preceding coverage is within one interval of its own
class: it is not silent, it is read rarely. A meter that does book its own
buckets and stops -- a dead hourly source, a sheet asked about a later
month -- still reads missing.

Auto answering twelve months with one bar. Coarse only means "longer than
a month", so a dipstick taken each autumn straddles a New Year as surely
as a month start: coarsening the chart to years bought nothing and cost
every point. The planning resolution now caps coarse at month when a run
crosses a local year edge, and a series that cannot resolve the natural
size no longer coarsens the whole chart -- it is drawn at that size with
its buckets marked, which the chart and table already explain.

A page contradicting itself. The comparison line above the ranking was fed
the leading measure's matched coverage but worded as if it spoke for the
page, directly above a burner row that did compare. It now names the figure
it is about.

Alongside: the "largest changes" ranking no longer drops a meter whose
change is not comparable. It ranks what can be ranked, then lists the rest
with their values and the reason -- the tank had been vanishing from its
own energy type. And every dated table now reads newest first, as lists
are read; charts stay chronological left to right, and the CSV export
stays ascending for spreadsheets.

A-41 to A-43 in the note record the three rules.
2026-09-20 12:44:32 +02:00

504 lines
21 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 MeterVault.Core.Analysis;
using static MeterVault.Core.Tests.Analysis.AnalysisClock;
namespace MeterVault.Core.Tests.Analysis;
/// <summary>
/// Buckets are local calendar units clipped to the period (D-05). What these pin down: "last 12 months" is
/// exactly 12 buckets ending at now; weeks start on Monday; DST days are 23 or 25 hours; Auto picks one
/// sensible size that never undercuts the data's resolution; an explicit size over 400 points is refused
/// with a coarser suggestion rather than truncated.
/// </summary>
public sealed class BucketPlannerTests
{
private static readonly DateTimeOffset September19 = At(Berlin, 2026, 9, 19, 14, 37);
private static ResolvedPeriod Preset(PeriodPreset preset, DateTimeOffset? now = null, TimeZoneInfo? zone = null) =>
PeriodResolver.Resolve(preset, null, null, now ?? September19, zone ?? Berlin);
private static ResolvedPeriod Custom(DateOnly first, DateOnly last, DateTimeOffset? now = null, TimeZoneInfo? zone = null) =>
PeriodResolver.Resolve(PeriodPreset.Custom, first, last, now ?? September19, zone ?? Berlin);
private static void AssertTiles(ResolvedPeriod period, IReadOnlyList<AnalysisBucket> buckets)
{
Assert.NotEmpty(buckets);
Assert.Equal(period.From, buckets[0].From);
Assert.Equal(period.To, buckets[^1].To);
Assert.Equal(period.FirstDay, buckets[0].FirstDay);
for (var i = 1; i < buckets.Count; i++)
{
Assert.Equal(buckets[i - 1].To, buckets[i].From);
Assert.Equal(buckets[i - 1].EndDay, buckets[i].FirstDay);
}
}
[Fact]
public void The_last_12_months_give_exactly_12_month_buckets_the_last_one_ending_now()
{
var period = Preset(PeriodPreset.Last12Months);
var plan = BucketPlanner.Plan(period, BucketSize.Month);
Assert.False(plan.Refused);
Assert.Equal(12, plan.Buckets.Count);
Assert.Equal(12, plan.PointCount);
AssertTiles(period, plan.Buckets);
Assert.Equal(
["2025-10", "2025-11", "2025-12", "2026-01", "2026-02", "2026-03", "2026-04", "2026-05", "2026-06", "2026-07", "2026-08", "2026-09"],
plan.Buckets.Select(b => $"{b.FirstDay.Year:D4}-{b.FirstDay.Month:D2}"));
var october = plan.Buckets[0];
Assert.Equal((Day(2025, 10, 1), Day(2025, 11, 1)), (october.FirstDay, october.EndDay));
Assert.Equal((Utc(2025, 9, 30, 22), Utc(2025, 10, 31, 23)), (october.From, october.To));
var september = plan.Buckets[^1];
Assert.Equal((Day(2026, 9, 1), Day(2026, 9, 20)), (september.FirstDay, september.EndDay));
Assert.Equal(September19, september.To);
Assert.All(plan.Buckets, b => Assert.Equal(BucketSize.Month, b.Size));
}
[Fact]
public void Across_New_Year_the_last_12_months_still_give_12_buckets_ending_with_the_new_January()
{
var period = Preset(PeriodPreset.Last12Months, At(Berlin, 2027, 1, 1, 0, 30));
var plan = BucketPlanner.Plan(period, BucketSize.Month);
Assert.Equal(12, plan.Buckets.Count);
Assert.Equal(Day(2026, 2, 1), plan.Buckets[0].FirstDay);
Assert.Equal(Day(2027, 1, 1), plan.Buckets[^1].FirstDay);
Assert.Equal(TimeSpan.FromMinutes(30), plan.Buckets[^1].To - plan.Buckets[^1].From);
}
[Fact]
public void At_the_exact_midnight_that_starts_a_month_the_last_12_months_keep_12_buckets_the_new_one_empty()
{
var period = Preset(PeriodPreset.Last12Months, At(Berlin, 2026, 10, 1));
var plan = BucketPlanner.Plan(period, BucketSize.Month);
Assert.Equal(12, plan.Buckets.Count);
Assert.Equal(Day(2026, 10, 1), plan.Buckets[^1].FirstDay);
Assert.Equal(plan.Buckets[^1].From, plan.Buckets[^1].To);
AssertTiles(period, plan.Buckets);
}
[Theory]
[InlineData(BucketSize.Day)]
[InlineData(BucketSize.Week)]
[InlineData(BucketSize.Month)]
[InlineData(BucketSize.Auto)]
public void At_the_exact_midnight_that_starts_a_month_month_to_date_has_one_empty_bucket_like_the_last_12_months(BucketSize size)
{
var now = At(Berlin, 2026, 10, 1);
var monthToDate = Preset(PeriodPreset.MonthToDate, now);
var last12 = Preset(PeriodPreset.Last12Months, now);
var plan = BucketPlanner.Plan(monthToDate, size);
// Both presets agree that October exists and is empty: no "not yet occurred" for one and an empty
// bucket for the other.
var today = Assert.Single(plan.Buckets);
Assert.Equal((Day(2026, 10, 1), now, now), (today.FirstDay, today.From, today.To));
Assert.Equal(1, plan.PointCount);
Assert.Equal(last12.To, BucketPlanner.Plan(last12, BucketSize.Month).Buckets[^1].To);
AssertTiles(monthToDate, plan.Buckets);
}
[Fact]
public void Week_buckets_start_on_Monday_and_the_first_one_starts_with_the_period()
{
// 1 September 2026 is a Tuesday.
var period = Preset(PeriodPreset.MonthToDate);
var plan = BucketPlanner.Plan(period, BucketSize.Week);
Assert.Equal(
[(Day(2026, 9, 1), Day(2026, 9, 7)), (Day(2026, 9, 7), Day(2026, 9, 14)), (Day(2026, 9, 14), Day(2026, 9, 20))],
plan.Buckets.Select(b => (b.FirstDay, b.EndDay)));
Assert.Equal(DayOfWeek.Tuesday, plan.Buckets[0].FirstDay.DayOfWeek);
Assert.All(plan.Buckets.Skip(1), b => Assert.Equal(DayOfWeek.Monday, b.FirstDay.DayOfWeek));
Assert.Equal(September19, plan.Buckets[^1].To);
AssertTiles(period, plan.Buckets);
}
[Fact]
public void A_range_from_Monday_to_Sunday_is_whole_weeks()
{
var period = Custom(Day(2026, 9, 7), Day(2026, 9, 20), At(Berlin, 2026, 10, 1, 12, 0));
var plan = BucketPlanner.Plan(period, BucketSize.Week);
Assert.Equal(2, plan.Buckets.Count);
Assert.All(plan.Buckets, b => Assert.Equal(7, b.EndDay.DayNumber - b.FirstDay.DayNumber));
Assert.Equal(Utc(2026, 9, 20, 22), plan.Buckets[^1].To);
}
[Fact]
public void Day_buckets_follow_local_midnight_so_the_spring_DST_day_has_23_hours()
{
var march = Preset(PeriodPreset.LastMonth, At(Berlin, 2026, 4, 2, 9, 0));
var plan = BucketPlanner.Plan(march, BucketSize.Day);
Assert.Equal(31, plan.Buckets.Count);
var dstDay = plan.Buckets.Single(b => b.FirstDay == Day(2026, 3, 29));
Assert.Equal((Utc(2026, 3, 28, 23), Utc(2026, 3, 29, 22)), (dstDay.From, dstDay.To));
Assert.Equal(TimeSpan.FromHours(23), dstDay.To - dstDay.From);
Assert.All(plan.Buckets.Where(b => b != dstDay), b => Assert.Equal(TimeSpan.FromHours(24), b.To - b.From));
AssertTiles(march, plan.Buckets);
}
[Fact]
public void Day_buckets_follow_local_midnight_so_the_autumn_DST_day_has_25_hours()
{
var october = Preset(PeriodPreset.LastMonth, At(Berlin, 2026, 11, 2, 9, 0));
var plan = BucketPlanner.Plan(october, BucketSize.Day);
var dstDay = plan.Buckets.Single(b => b.FirstDay == Day(2026, 10, 25));
Assert.Equal((Utc(2026, 10, 24, 22), Utc(2026, 10, 25, 23)), (dstDay.From, dstDay.To));
Assert.Equal(TimeSpan.FromHours(25), dstDay.To - dstDay.From);
}
[Fact]
public void Behind_UTC_month_buckets_start_at_New_York_midnight()
{
var period = Custom(Day(2026, 10, 1), Day(2026, 11, 30), zone: NewYork, now: At(NewYork, 2026, 12, 15, 12, 0));
var plan = BucketPlanner.Plan(period, BucketSize.Month);
Assert.Equal(
[(Utc(2026, 10, 1, 4), Utc(2026, 11, 1, 4)), (Utc(2026, 11, 1, 4), Utc(2026, 12, 1, 5))],
plan.Buckets.Select(b => (b.From, b.To)));
}
[Fact]
public void A_custom_range_past_now_gets_buckets_only_up_to_now()
{
var period = Custom(Day(2026, 9, 1), Day(2026, 12, 31));
var plan = BucketPlanner.Plan(period, BucketSize.Month);
var only = Assert.Single(plan.Buckets);
Assert.Equal((Day(2026, 9, 1), Day(2026, 9, 20)), (only.FirstDay, only.EndDay));
Assert.Equal(September19, only.To);
}
[Fact]
public void A_period_that_has_not_started_or_has_no_history_has_no_buckets()
{
var future = Custom(Day(2027, 1, 1), Day(2027, 3, 31));
var none = PeriodResolver.Resolve(PeriodPreset.AllHistory, null, null, September19, Berlin);
foreach (var period in new[] { future, none })
{
foreach (var size in new[] { BucketSize.Auto, BucketSize.Day, BucketSize.Year })
{
var plan = BucketPlanner.Plan(period, size);
Assert.Empty(plan.Buckets);
Assert.False(plan.Refused);
Assert.Equal(0, plan.PointCount);
}
}
}
[Theory]
[InlineData(PeriodPreset.MonthToDate, BucketSize.Day, 19)]
[InlineData(PeriodPreset.LastMonth, BucketSize.Day, 31)]
[InlineData(PeriodPreset.YearToDate, BucketSize.Month, 9)]
[InlineData(PeriodPreset.Last12Months, BucketSize.Month, 12)]
[InlineData(PeriodPreset.Last24Months, BucketSize.Month, 24)]
[InlineData(PeriodPreset.PreviousYear, BucketSize.Month, 12)]
public void Auto_picks_days_for_short_ranges_and_months_for_a_year_or_more(PeriodPreset preset, BucketSize expected, int points)
{
var plan = BucketPlanner.Plan(Preset(preset), BucketSize.Auto);
Assert.Equal(BucketSize.Auto, plan.Requested);
Assert.Equal(expected, plan.Size);
Assert.Equal(points, plan.Buckets.Count);
Assert.False(plan.Refused);
}
[Theory]
[InlineData(62, BucketSize.Day)]
[InlineData(63, BucketSize.Week)]
[InlineData(182, BucketSize.Week)]
[InlineData(183, BucketSize.Month)]
public void Auto_switches_from_days_to_weeks_after_62_days_and_to_months_after_26_weeks(int days, BucketSize expected)
{
var first = Day(2025, 1, 1);
var period = Custom(first, first.AddDays(days - 1));
Assert.Equal(expected, BucketPlanner.Plan(period, BucketSize.Auto).Size);
}
[Theory]
[InlineData(1, 1)]
[InlineData(2, 15)]
[InlineData(3, 2)]
[InlineData(6, 15)]
[InlineData(7, 2)]
[InlineData(9, 19)]
[InlineData(12, 31)]
public void Auto_charts_year_to_date_by_month_on_every_day_of_the_year(int month, int day)
{
// Chosen from the named year, not the elapsed part (A-06): the same URL used to render by day until
// 2 March and by week until 1 July.
var period = Preset(PeriodPreset.YearToDate, At(Berlin, 2026, month, day, 12, 0));
var plan = BucketPlanner.Plan(period, BucketSize.Auto);
Assert.Equal(BucketSize.Month, plan.Size);
Assert.Equal(month, plan.Buckets.Count);
}
[Fact]
public void Auto_charts_month_to_date_by_day_even_on_the_1st()
{
var plan = BucketPlanner.Plan(Preset(PeriodPreset.MonthToDate, At(Berlin, 2026, 9, 1, 8, 0)), BucketSize.Auto);
Assert.Equal(BucketSize.Day, plan.Size);
Assert.Single(plan.Buckets);
}
[Fact]
public void Auto_sizes_a_custom_range_reaching_past_now_by_the_range_asked_for()
{
var first = Day(2026, 9, 1);
var last = Day(2026, 12, 31);
var inSeptember = BucketPlanner.Plan(Custom(first, last, At(Berlin, 2026, 9, 19, 12, 0)), BucketSize.Auto);
var inNovember = BucketPlanner.Plan(Custom(first, last, At(Berlin, 2026, 11, 20, 12, 0)), BucketSize.Auto);
Assert.Equal(BucketSize.Week, inSeptember.Size);
Assert.Equal(BucketSize.Week, inNovember.Size);
}
[Fact]
public void Auto_checks_the_point_limit_on_the_buckets_that_exist_up_to_now()
{
// The named year has 12 months, over a limit of 9; only the 9 that exist by 19 September are counted.
var plan = BucketPlanner.Plan(Preset(PeriodPreset.YearToDate), BucketSize.Auto, maxPoints: 9);
Assert.Equal(BucketSize.Month, plan.Size);
Assert.Equal(9, plan.PointCount);
Assert.False(plan.Refused);
}
[Fact]
public void Auto_weighs_months_against_the_point_limit_it_was_given()
{
// 501 months since 1985: over the default 400, within a limit of 1,000.
var since1985 = PeriodResolver.Resolve(PeriodPreset.AllHistory, null, null, September19, Berlin, Day(1985, 1, 1));
var generous = BucketPlanner.Plan(since1985, BucketSize.Auto, maxPoints: 1_000);
var standard = BucketPlanner.Plan(since1985, BucketSize.Auto);
Assert.Equal(BucketSize.Month, generous.Size);
Assert.Equal(501, generous.Buckets.Count);
Assert.Equal(BucketSize.Year, standard.Size);
}
[Fact]
public void Auto_charts_all_history_from_a_stray_ancient_reading_by_year_instead_of_refusing()
{
var period = PeriodResolver.Resolve(PeriodPreset.AllHistory, null, null, September19, Berlin, Day(206, 5, 1));
var plan = BucketPlanner.Plan(period, BucketSize.Auto);
Assert.False(plan.Refused);
Assert.Equal(BucketSize.Year, plan.Size);
Assert.Equal(2026 - 1900 + 1, plan.Buckets.Count);
}
[Fact]
public void Auto_uses_months_up_to_400_of_them_and_years_beyond()
{
// The reference data's oil history starts in 1997: 357 months to September 2026.
var since1997 = PeriodResolver.Resolve(PeriodPreset.AllHistory, null, null, September19, Berlin, Day(1997, 1, 1));
var since1900 = PeriodResolver.Resolve(PeriodPreset.AllHistory, null, null, September19, Berlin, Day(1900, 1, 1));
var monthly = BucketPlanner.Plan(since1997, BucketSize.Auto);
var yearly = BucketPlanner.Plan(since1900, BucketSize.Auto);
Assert.Equal(BucketSize.Month, monthly.Size);
Assert.Equal(357, monthly.Buckets.Count);
Assert.Equal(BucketSize.Year, yearly.Size);
Assert.Equal(127, yearly.Buckets.Count);
}
[Fact]
public void Auto_never_goes_finer_than_the_coarsest_resolution_a_series_needs()
{
var monthToDate = Preset(PeriodPreset.MonthToDate);
// A monthly import in a daily chart would be nothing but unresolved buckets.
var monthly = BucketPlanner.Plan(monthToDate, BucketSize.Auto, ResolutionClass.Month);
var weekly = BucketPlanner.Plan(monthToDate, BucketSize.Auto, ResolutionClass.Week);
var coarse = BucketPlanner.Plan(Preset(PeriodPreset.Last12Months), BucketSize.Auto, ResolutionClass.Coarse);
var month = Assert.Single(monthly.Buckets);
Assert.Equal((Utc(2026, 8, 31, 22), September19), (month.From, month.To));
Assert.Equal(BucketSize.Week, weekly.Size);
Assert.Equal(BucketSize.Year, coarse.Size);
Assert.Equal([Day(2025, 10, 1), Day(2026, 1, 1)], coarse.Buckets.Select(b => b.FirstDay));
}
[Fact]
public void Auto_keeps_the_length_based_default_when_the_data_is_finer()
{
// Hourly data does not turn a year into 365 bars; the user can still ask for days explicitly.
var plan = BucketPlanner.Plan(Preset(PeriodPreset.Last12Months), BucketSize.Auto, ResolutionClass.Hour);
Assert.Equal(BucketSize.Month, plan.Size);
}
[Fact]
public void Auto_coarsens_until_the_plan_fits_a_smaller_point_limit()
{
var plan = BucketPlanner.Plan(Preset(PeriodPreset.MonthToDate), BucketSize.Auto, maxPoints: 10);
Assert.Equal(BucketSize.Week, plan.Size);
Assert.Equal(3, plan.Buckets.Count);
Assert.False(plan.Refused);
}
[Fact]
public void An_explicit_day_bucket_over_400_points_is_refused_with_weeks_suggested()
{
var period = Preset(PeriodPreset.Last24Months);
var plan = BucketPlanner.Plan(period, BucketSize.Day);
Assert.True(plan.Refused);
Assert.Empty(plan.Buckets);
Assert.Equal(BucketSize.Day, plan.Size);
Assert.Equal(Day(2026, 9, 19).DayNumber - Day(2024, 10, 1).DayNumber + 1, plan.PointCount);
Assert.Equal(BucketSize.Week, plan.Suggested);
Assert.False(BucketPlanner.Plan(period, BucketSize.Week).Refused);
}
[Fact]
public void A_refusal_suggests_nothing_finer_than_the_data_resolves()
{
var plan = BucketPlanner.Plan(Preset(PeriodPreset.Last24Months), BucketSize.Day, ResolutionClass.Month);
Assert.True(plan.Refused);
Assert.Equal(BucketSize.Month, plan.Suggested);
}
[Fact]
public void A_refusal_over_centuries_suggests_years()
{
var period = Custom(Day(1950, 1, 1), Day(2025, 12, 31));
var plan = BucketPlanner.Plan(period, BucketSize.Month);
Assert.True(plan.Refused);
Assert.Equal(76 * 12, plan.PointCount);
Assert.Equal(BucketSize.Year, plan.Suggested);
}
[Fact]
public void An_explicit_size_within_the_limit_is_honoured_even_finer_than_the_data()
{
var period = Preset(PeriodPreset.Last12Months);
var plan = BucketPlanner.Plan(period, BucketSize.Day, ResolutionClass.Month);
Assert.False(plan.Refused);
Assert.Equal(Day(2026, 9, 19).DayNumber - Day(2025, 10, 1).DayNumber + 1, plan.Buckets.Count);
AssertTiles(period, plan.Buckets);
}
[Fact]
public void Every_size_counts_its_buckets_without_building_them()
{
var periods = new[]
{
Preset(PeriodPreset.MonthToDate),
Preset(PeriodPreset.Last24Months),
Preset(PeriodPreset.PreviousYear),
Custom(Day(2023, 12, 31), Day(2025, 1, 1)),
Preset(PeriodPreset.YearToDate, At(NewYork, 2026, 3, 8, 12, 0), NewYork),
};
foreach (var period in periods)
{
foreach (var size in new[] { BucketSize.Day, BucketSize.Week, BucketSize.Month, BucketSize.Year })
{
var plan = BucketPlanner.Plan(period, size, maxPoints: 10_000);
Assert.Equal(BucketPlanner.CountBuckets(period, size), plan.Buckets.Count);
AssertTiles(period, plan.Buckets);
}
}
}
[Fact]
public void The_current_month_bucket_is_cut_short_at_now_and_names_the_whole_month_to_drill_into()
{
// Drilling into "1 19 Sep" as a custom range would compare with 13 31 August; the whole month
// compares with 1 19 August, like month to date (D-51).
foreach (var preset in new[] { PeriodPreset.MonthToDate, PeriodPreset.YearToDate, PeriodPreset.Last12Months })
{
var plan = BucketPlanner.Plan(Preset(preset), BucketSize.Month);
var september = plan.Buckets[^1];
Assert.Equal((Day(2026, 9, 20), Day(2026, 10, 1)), (september.EndDay, september.NominalEndDay));
Assert.True(september.IsCutShort);
Assert.All(plan.Buckets.SkipLast(1), b => Assert.False(b.IsCutShort));
Assert.All(plan.Buckets.SkipLast(1), b => Assert.Null(b.NominalEndDay));
}
}
[Fact]
public void A_week_cut_at_now_names_its_Sunday_and_a_day_is_never_cut_short()
{
var weeks = BucketPlanner.Plan(Preset(PeriodPreset.MonthToDate), BucketSize.Week);
var days = BucketPlanner.Plan(Preset(PeriodPreset.MonthToDate), BucketSize.Day);
Assert.Equal(Day(2026, 9, 21), weeks.Buckets[^1].NominalEndDay);
Assert.All(days.Buckets, b => Assert.False(b.IsCutShort));
}
[Fact]
public void The_whole_unit_of_a_cut_bucket_never_reaches_past_the_range_that_was_asked_for()
{
var reachingPastNow = BucketPlanner.Plan(Custom(Day(2026, 9, 1), Day(2026, 9, 25)), BucketSize.Month);
var complete = BucketPlanner.Plan(Custom(Day(2026, 8, 10), Day(2026, 9, 15), At(Berlin, 2026, 10, 1, 12, 0)), BucketSize.Month);
Assert.Equal(Day(2026, 9, 26), Assert.Single(reachingPastNow.Buckets).NominalEndDay);
// A range that ends mid-month by request is not cut short: its last bucket is all it names.
Assert.All(complete.Buckets, b => Assert.False(b.IsCutShort));
Assert.Equal(Day(2026, 9, 16), complete.Buckets[^1].EndDay);
}
[Fact]
public void At_midnight_the_empty_current_month_still_names_the_whole_month()
{
var plan = BucketPlanner.Plan(Preset(PeriodPreset.Last12Months, At(Berlin, 2026, 10, 1)), BucketSize.Month);
Assert.Equal((Day(2026, 10, 2), Day(2026, 11, 1)), (plan.Buckets[^1].EndDay, plan.Buckets[^1].NominalEndDay));
}
[Fact]
public void The_point_limit_must_allow_at_least_one_point()
{
Assert.Throws<ArgumentOutOfRangeException>(() => BucketPlanner.Plan(Preset(PeriodPreset.MonthToDate), BucketSize.Day, maxPoints: 0));
}
[Theory]
[InlineData(ResolutionClass.Hour, BucketSize.Day)]
[InlineData(ResolutionClass.Day, BucketSize.Day)]
[InlineData(ResolutionClass.Week, BucketSize.Week)]
[InlineData(ResolutionClass.Month, BucketSize.Month)]
[InlineData(ResolutionClass.Coarse, BucketSize.Year)]
public void Each_resolution_class_maps_to_the_finest_bucket_it_can_fill(ResolutionClass resolution, BucketSize expected)
{
Assert.Equal(expected, BucketPlanner.MinimumSizeFor(resolution));
}
}