WorkTracker/tests/WorkTracker.Tests/MonthlyTimesheetExcelExporterTests.cs
2026-04-24 10:45:44 +02:00

119 lines
4 KiB
C#

using ClosedXML.Excel;
using WorkTracker.Domain;
using WorkTracker.Services.Exports;
using Xunit;
namespace WorkTracker.Tests;
public sealed class MonthlyTimesheetExcelExporterTests
{
[Fact]
public void Export_ForApril2026EmptyTimesheet_MatchesExpectedWorkbook()
{
var exporter = new MonthlyTimesheetExcelExporter();
var timesheet = CreateTimesheet(new DateOnly(2026, 4, 1), new HashSet<DateOnly>
{
new(2026, 4, 6),
new(2026, 4, 25)
});
var templatePath = GetTemplatePath();
using var templateStream = File.OpenRead(templatePath);
var workbookBytes = exporter.Export(timesheet, templateStream);
WorkbookAssert.Equivalent(GetExpectedWorkbookPath(), workbookBytes);
}
[Fact]
public void Export_ForThirtyOneDayMonth_ShiftsTotalColumnAfterLastDay()
{
var exporter = new MonthlyTimesheetExcelExporter();
var timesheet = CreateTimesheet(new DateOnly(2026, 5, 1), new HashSet<DateOnly>());
var templatePath = GetTemplatePath();
using var templateStream = File.OpenRead(templatePath);
var workbookBytes = exporter.Export(timesheet, templateStream);
using var workbook = new XLWorkbook(new MemoryStream(workbookBytes));
var worksheet = workbook.Worksheet(1);
Assert.Equal(31d, worksheet.Cell("AG1").GetDouble());
Assert.Equal("SUM(C2:AG2)", worksheet.Cell("AH2").FormulaA1);
Assert.Equal("TOTALE", worksheet.Cell("AH1").GetString());
}
private static MonthlyTimesheetModel CreateTimesheet(DateOnly monthStart, ISet<DateOnly> holidays)
{
var lastDay = monthStart.AddMonths(1).AddDays(-1);
var days = new List<MonthlyTimesheetDayModel>();
for (var date = monthStart; date <= lastDay; date = date.AddDays(1))
{
days.Add(new MonthlyTimesheetDayModel
{
Date = date,
IsWeekend = date.DayOfWeek is DayOfWeek.Saturday or DayOfWeek.Sunday,
IsHoliday = holidays.Contains(date)
});
}
return new MonthlyTimesheetModel
{
Year = monthStart.Year,
Month = monthStart.Month,
Days = days,
Rows =
[
CreateRow("office", days.Count),
CreateRow("home", days.Count),
CreateRow("overtime", days.Count),
CreateRow("weekend", days.Count),
CreateRow("night", days.Count),
CreateRow("vacation", days.Count),
CreateRow("permit", days.Count),
CreateRow("compensatory-rest", days.Count),
CreateRow("sick", days.Count),
CreateRow("holiday", days.Count)
]
};
}
private static MonthlyTimesheetRowModel CreateRow(string key, int dayCount)
{
return new MonthlyTimesheetRowModel
{
Key = key,
DailyValues = Enumerable.Repeat<decimal?>(null, dayCount).ToList()
};
}
private static string GetExpectedWorkbookPath()
{
var repositoryRoot = FindRepositoryRoot();
var candidate = Path.Combine(repositoryRoot, "tests", "WorkTracker.Tests", "Expected", "monthly-timesheet-2026-04-empty.expected.xlsx");
return File.Exists(candidate)
? candidate
: GetTemplatePath();
}
private static string GetTemplatePath()
{
var repositoryRoot = FindRepositoryRoot();
return Path.Combine(repositoryRoot, "Templates", "monthly-timesheet-template.xlsx");
}
private static string FindRepositoryRoot()
{
var directory = new DirectoryInfo(AppContext.BaseDirectory);
while (directory is not null)
{
if (File.Exists(Path.Combine(directory.FullName, "WorkTracker.sln")))
{
return directory.FullName;
}
directory = directory.Parent;
}
throw new DirectoryNotFoundException("Unable to locate the WorkTracker repository root.");
}
}