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 { 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()); 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 holidays) { var lastDay = monthStart.AddMonths(1).AddDays(-1); var days = new List(); 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(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."); } }