From 6952f686fae4c8cd299a73d938808667a76c9c05 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Wed, 7 Jan 2026 03:55:33 -0500 Subject: [PATCH] perf: optimize ExcelIO tests with fixture-based consolidation - Add WorkbookFixtureBase and 4 concrete fixtures for shared workbooks - Add ExcelTestHelpers with shared utility methods - Create Integration/ folder with 7 fixture-based test classes: - MinimalSearchTests (5 tests) - SearchResultsSheetTests (5 tests) - MisInfoSheetTests (11 tests) - InvestigationSheetTests (7 tests) - ProtectionAndStyleTests (7 tests) - LegacyFormatTests (5 tests) - LargeDataSetTests (1 test) - Delete redundant ExcelExportIntegrationTests.cs (26 tests) - Delete redundant LegacyComparisonTests.cs (16 tests) - Reduce workbook generations from ~42 to 4 fixtures - Test runtime reduced from ~18 min to ~4 min (76% improvement) - All 122 ExcelIO tests pass --- .../2026-01-07-excelio-test-optimization.md | 1030 +++++++++++++++++ .../ExcelExportIntegrationTests.cs | 682 ----------- .../Fixtures/ExcelTestHelpers.cs | 18 + .../Fixtures/LargeDataSetFixture.cs | 35 + .../Fixtures/MinimalSearchFixture.cs | 18 + .../Fixtures/WithMisDataFixture.cs | 87 ++ .../Fixtures/WithResultsFixture.cs | 43 + .../Fixtures/WorkbookFixtureBase.cs | 68 ++ .../Integration/InvestigationSheetTests.cs | 86 ++ .../Integration/LargeDataSetTests.cs | 24 + .../Integration/LegacyFormatTests.cs | 41 + .../Integration/MinimalSearchTests.cs | 48 + .../Integration/MisInfoSheetTests.cs | 130 +++ .../Integration/ProtectionAndStyleTests.cs | 75 ++ .../Integration/SearchResultsSheetTests.cs | 89 ++ .../LegacyComparisonTests.cs | 527 --------- 16 files changed, 1792 insertions(+), 1209 deletions(-) create mode 100644 NEW/docs/plans/2026-01-07-excelio-test-optimization.md delete mode 100644 NEW/tests/JdeScoping.ExcelIO.Tests/ExcelExportIntegrationTests.cs create mode 100644 NEW/tests/JdeScoping.ExcelIO.Tests/Fixtures/ExcelTestHelpers.cs create mode 100644 NEW/tests/JdeScoping.ExcelIO.Tests/Fixtures/LargeDataSetFixture.cs create mode 100644 NEW/tests/JdeScoping.ExcelIO.Tests/Fixtures/MinimalSearchFixture.cs create mode 100644 NEW/tests/JdeScoping.ExcelIO.Tests/Fixtures/WithMisDataFixture.cs create mode 100644 NEW/tests/JdeScoping.ExcelIO.Tests/Fixtures/WithResultsFixture.cs create mode 100644 NEW/tests/JdeScoping.ExcelIO.Tests/Fixtures/WorkbookFixtureBase.cs create mode 100644 NEW/tests/JdeScoping.ExcelIO.Tests/Integration/InvestigationSheetTests.cs create mode 100644 NEW/tests/JdeScoping.ExcelIO.Tests/Integration/LargeDataSetTests.cs create mode 100644 NEW/tests/JdeScoping.ExcelIO.Tests/Integration/LegacyFormatTests.cs create mode 100644 NEW/tests/JdeScoping.ExcelIO.Tests/Integration/MinimalSearchTests.cs create mode 100644 NEW/tests/JdeScoping.ExcelIO.Tests/Integration/MisInfoSheetTests.cs create mode 100644 NEW/tests/JdeScoping.ExcelIO.Tests/Integration/ProtectionAndStyleTests.cs create mode 100644 NEW/tests/JdeScoping.ExcelIO.Tests/Integration/SearchResultsSheetTests.cs delete mode 100644 NEW/tests/JdeScoping.ExcelIO.Tests/LegacyComparisonTests.cs diff --git a/NEW/docs/plans/2026-01-07-excelio-test-optimization.md b/NEW/docs/plans/2026-01-07-excelio-test-optimization.md new file mode 100644 index 0000000..520e898 --- /dev/null +++ b/NEW/docs/plans/2026-01-07-excelio-test-optimization.md @@ -0,0 +1,1030 @@ +# ExcelIO Test Optimization Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Reduce ExcelIO.Tests runtime from ~18 minutes to ~3-4 minutes by using fixture-based test consolidation. + +**Architecture:** Replace per-test workbook generation with xUnit `IClassFixture` pattern. Generate 4 shared workbooks once, reuse across 42 integration tests. + +**Tech Stack:** xUnit fixtures, ClosedXML, existing ExcelExportService + +--- + +## Task 1: Create Fixture Base Class and Infrastructure + +**Files:** +- Create: `tests/JdeScoping.ExcelIO.Tests/Fixtures/WorkbookFixtureBase.cs` +- Create: `tests/JdeScoping.ExcelIO.Tests/Fixtures/ExcelTestHelpers.cs` + +**Step 1: Create Fixtures directory** + +```bash +mkdir -p tests/JdeScoping.ExcelIO.Tests/Fixtures +``` + +**Step 2: Write WorkbookFixtureBase.cs** + +```csharp +using ClosedXML.Excel; +using JdeScoping.ExcelIO.Generators; +using JdeScoping.ExcelIO.Mapping; +using JdeScoping.ExcelIO.Mapping.Maps; +using JdeScoping.ExcelIO.Models.Reporting; +using JdeScoping.ExcelIO.Options; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using NSubstitute; + +namespace JdeScoping.ExcelIO.Tests.Fixtures; + +public abstract class WorkbookFixtureBase : IDisposable +{ + public XLWorkbook Workbook { get; } + public SearchModel SearchModel { get; } + + protected abstract SearchModel CreateSearchModel(); + + protected WorkbookFixtureBase() + { + SearchModel = CreateSearchModel(); + var service = CreateExportService(); + var bytes = service.GenerateAsync(SearchModel).GetAwaiter().GetResult(); + Workbook = new XLWorkbook(new MemoryStream(bytes)); + } + + private static ExcelExportService CreateExportService() + { + var logger = Substitute.For>(); + var options = Options.Create(new ExcelExportOptions + { + CriteriaSheetPassword = "TestCriteriaPass", + DataSheetPassword = "TestDataPass" + }); + + var registry = CreateTestRegistry(); + var tableWriter = new FluentTableWriter(registry); + var criteriaGenerator = new CriteriaSheetGenerator(options, tableWriter); + + return new ExcelExportService(logger, options, criteriaGenerator, tableWriter, registry); + } + + private static ExcelMapRegistry CreateTestRegistry() + { + var registry = new ExcelMapRegistry(); + + registry.Register(new SearchResultMap()); + registry.Register(new MisSearchResultMap()); + registry.Register(new MisNonMatchSearchResultMap()); + registry.Register(new TimespanFilterMap()); + registry.Register(new WorkOrderFilterEntryMap()); + registry.Register(new ItemNumberFilterEntryMap()); + registry.Register(new ProfitCenterFilterEntryMap()); + registry.Register(new WorkCenterFilterEntryMap()); + registry.Register(new OperatorFilterEntryMap()); + registry.Register(new ComponentLotFilterEntryMap()); + registry.Register(new ItemOperationMisFilterEntryMap()); + + return registry; + } + + public void Dispose() + { + Workbook.Dispose(); + GC.SuppressFinalize(this); + } +} +``` + +**Step 3: Write ExcelTestHelpers.cs** + +```csharp +using ClosedXML.Excel; + +namespace JdeScoping.ExcelIO.Tests.Fixtures; + +public static class ExcelTestHelpers +{ + public static List GetHeadersFromSheet(IXLWorksheet sheet) + { + var headers = new List(); + var col = 1; + while (!sheet.Cell(1, col).IsEmpty()) + { + headers.Add(sheet.Cell(1, col).Value.GetText()); + col++; + } + return headers; + } +} +``` + +**Step 4: Build and verify** + +```bash +dotnet build tests/JdeScoping.ExcelIO.Tests +``` + +--- + +## Task 2: Create Concrete Fixture Classes + +**Files:** +- Create: `tests/JdeScoping.ExcelIO.Tests/Fixtures/MinimalSearchFixture.cs` +- Create: `tests/JdeScoping.ExcelIO.Tests/Fixtures/WithResultsFixture.cs` +- Create: `tests/JdeScoping.ExcelIO.Tests/Fixtures/WithMisDataFixture.cs` +- Create: `tests/JdeScoping.ExcelIO.Tests/Fixtures/LargeDataSetFixture.cs` + +**Step 1: Write MinimalSearchFixture.cs** + +```csharp +using JdeScoping.ExcelIO.Models.Reporting; + +namespace JdeScoping.ExcelIO.Tests.Fixtures; + +public class MinimalSearchFixture : WorkbookFixtureBase +{ + protected override SearchModel CreateSearchModel() => new() + { + Id = 1, + Name = "Minimal Search Test", + UserName = "testuser", + SubmitDt = new DateTime(2024, 1, 15, 14, 30, 45), + StartDt = new DateTime(2024, 1, 15, 14, 31, 0), + EndDt = new DateTime(2024, 1, 15, 14, 35, 0), + ExtractMisData = false, + Results = [] + }; +} +``` + +**Step 2: Write WithResultsFixture.cs** + +```csharp +using JdeScoping.ExcelIO.Models.Reporting; +using SearchResult = JdeScoping.Core.Models.SearchResults.SearchResult; + +namespace JdeScoping.ExcelIO.Tests.Fixtures; + +public class WithResultsFixture : WorkbookFixtureBase +{ + protected override SearchModel CreateSearchModel() => new() + { + Id = 1, + Name = "Search With Results", + UserName = "testuser", + SubmitDt = new DateTime(2024, 1, 15, 14, 30, 45), + StartDt = new DateTime(2024, 1, 15, 14, 31, 0), + EndDt = new DateTime(2024, 1, 15, 14, 35, 0), + ExtractMisData = false, + Results = + [ + new SearchResult + { + WorkOrderNumber = 12345, + WorkOrderBranchCode = "001", + LotNumber = "LOT-001", + ItemNumber = "ITEM-001", + PlanningFamily = "PF01", + StockingType = "M", + OrderQuantity = 100, + HeldQuantity = 0, + ScrappedQuantity = 0, + ShippedQuantity = 50, + StepBranchCode = "001", + StepNumber = 10, + StepDescription = "Assembly", + FunctionOperationDescription = "Main assembly operation", + StepUpdateDt = new DateTime(2024, 1, 14, 10, 0, 0), + StatusCode = "50", + StatusDescription = "In Progress", + StatusUpdateDt = new DateTime(2024, 1, 14, 10, 0, 0), + Flagged = true + } + ] + }; +} +``` + +**Step 3: Write WithMisDataFixture.cs** + +```csharp +using JdeScoping.ExcelIO.Models.Reporting; +using SearchResult = JdeScoping.Core.Models.SearchResults.SearchResult; +using MisSearchResult = JdeScoping.Core.Models.SearchResults.MisSearchResult; +using MisNonMatchSearchResult = JdeScoping.Core.Models.SearchResults.MisNonMatchSearchResult; + +namespace JdeScoping.ExcelIO.Tests.Fixtures; + +public class WithMisDataFixture : WorkbookFixtureBase +{ + protected override SearchModel CreateSearchModel() => new() + { + Id = 1, + Name = "Search With MIS Data", + UserName = "testuser", + SubmitDt = new DateTime(2024, 1, 15, 14, 30, 45), + StartDt = new DateTime(2024, 1, 15, 14, 31, 0), + EndDt = new DateTime(2024, 1, 15, 14, 35, 0), + ExtractMisData = true, + Results = + [ + new SearchResult + { + WorkOrderNumber = 12345, + WorkOrderBranchCode = "001", + LotNumber = "LOT-001", + ItemNumber = "ITEM-001", + PlanningFamily = "PF01", + StockingType = "M", + OrderQuantity = 100, + HeldQuantity = 0, + ScrappedQuantity = 0, + ShippedQuantity = 50, + StepBranchCode = "001", + StepNumber = 10, + StepDescription = "Assembly", + FunctionOperationDescription = "Main assembly", + StepUpdateDt = new DateTime(2024, 1, 14, 10, 0, 0), + StatusCode = "50", + StatusDescription = "In Progress", + Flagged = true + } + ], + MisResults = + [ + new MisSearchResult + { + ItemNumber = "ITEM-001", + SequenceNumber = "010", + MisNumber = "MIS-001", + RevId = "A", + ItemDescription = "Test Item", + Status = "Released", + ReleaseDate = new DateTime(2023, 12, 15), + BranchCode = "001", + JobStepSequenceNumber = 10, + MatchedSequenceNumber = 10, + RoutingMatch = true, + MasterMatch = true, + FunctionOperationDescription = "Assembly operation", + CharNumber = "001", + TestDescription = "Sample test description", + SamplingType = "100%", + SamplingValue = "1", + ToolsGauges = "Gauge A, Gauge B", + WorkInstructions = "Step 1: Do this. Step 2: Do that." + } + ], + MisNonMatchResults = + [ + new MisNonMatchSearchResult + { + WorkCenterCode = "WC01", + WorkOrderNumber = 12345, + WorkOrderStartDate = new DateTime(2024, 1, 8), + JobStepNumber = 10, + JobStepDescription = "Test operation", + JobStepEndDate = new DateTime(2024, 1, 10), + FunctionCode = "FC01", + WasJobStepAdded = false, + MatchedJobStepNumber = 10, + ItemNumber = "ITEM-001", + ItemDescription = "Test Item Description", + RoutingType = "M" + } + ] + }; +} +``` + +**Step 4: Write LargeDataSetFixture.cs** + +```csharp +using JdeScoping.ExcelIO.Models.Reporting; +using SearchResult = JdeScoping.Core.Models.SearchResults.SearchResult; + +namespace JdeScoping.ExcelIO.Tests.Fixtures; + +public class LargeDataSetFixture : WorkbookFixtureBase +{ + protected override SearchModel CreateSearchModel() + { + var model = new SearchModel + { + Id = 1, + Name = "Large Data Set Test", + UserName = "testuser", + SubmitDt = new DateTime(2024, 1, 15, 14, 30, 45), + StartDt = new DateTime(2024, 1, 15, 14, 31, 0), + EndDt = new DateTime(2024, 1, 15, 14, 35, 0), + ExtractMisData = false, + Results = [] + }; + + for (int i = 0; i < 1000; i++) + { + model.Results.Add(new SearchResult + { + WorkOrderNumber = 10000 + i, + ItemNumber = $"ITEM-{i:D5}", + LotNumber = $"LOT-{i:D5}", + Flagged = true + }); + } + + return model; + } +} +``` + +**Step 5: Build and verify** + +```bash +dotnet build tests/JdeScoping.ExcelIO.Tests +``` + +--- + +## Task 3: Create MinimalSearchTests + +**Files:** +- Create: `tests/JdeScoping.ExcelIO.Tests/Integration/MinimalSearchTests.cs` + +**Step 1: Create Integration directory** + +```bash +mkdir -p tests/JdeScoping.ExcelIO.Tests/Integration +``` + +**Step 2: Write MinimalSearchTests.cs** + +```csharp +using ClosedXML.Excel; +using JdeScoping.ExcelIO.Tests.Fixtures; +using Shouldly; +using Xunit; + +namespace JdeScoping.ExcelIO.Tests.Integration; + +public class MinimalSearchTests : IClassFixture +{ + private readonly XLWorkbook _workbook; + + public MinimalSearchTests(MinimalSearchFixture fixture) + { + _workbook = fixture.Workbook; + } + + [Fact] + public void SheetCount_IsTwo() + { + _workbook.Worksheets.Count.ShouldBe(2); + } + + [Fact] + public void SearchCriteriaSheet_Exists() + { + _workbook.Worksheets.TryGetWorksheet("Search Criteria", out _).ShouldBeTrue(); + } + + [Fact] + public void SearchResultsSheet_Exists() + { + _workbook.Worksheets.TryGetWorksheet("Search Results", out _).ShouldBeTrue(); + } + + [Fact] + public void SearchCriteriaSheet_IsProtected() + { + var sheet = _workbook.Worksheet("Search Criteria"); + sheet.Protection.IsProtected.ShouldBeTrue(); + } + + [Fact] + public void SearchResultsSheet_IsProtected() + { + var sheet = _workbook.Worksheet("Search Results"); + sheet.Protection.IsProtected.ShouldBeTrue(); + } +} +``` + +**Step 3: Build and run tests** + +```bash +dotnet build tests/JdeScoping.ExcelIO.Tests +dotnet test tests/JdeScoping.ExcelIO.Tests --filter "FullyQualifiedName~MinimalSearchTests" --verbosity normal +``` + +--- + +## Task 4: Create SearchResultsSheetTests + +**Files:** +- Create: `tests/JdeScoping.ExcelIO.Tests/Integration/SearchResultsSheetTests.cs` + +**Step 1: Write SearchResultsSheetTests.cs** + +```csharp +using ClosedXML.Excel; +using JdeScoping.ExcelIO.Tests.Fixtures; +using Shouldly; +using Xunit; + +namespace JdeScoping.ExcelIO.Tests.Integration; + +public class SearchResultsSheetTests : IClassFixture +{ + private readonly XLWorkbook _workbook; + private readonly IXLWorksheet _sheet; + private readonly List _headers; + + public SearchResultsSheetTests(WithResultsFixture fixture) + { + _workbook = fixture.Workbook; + _sheet = _workbook.Worksheet("Search Results"); + _headers = ExcelTestHelpers.GetHeadersFromSheet(_sheet); + } + + [Fact] + public void ColumnCount_Is19() + { + _headers.Count.ShouldBe(19); + } + + [Fact] + public void ColumnHeaders_MatchExpected() + { + _headers.ShouldContain("Work Order Number"); + _headers.ShouldContain("Work Order Branch Code"); + _headers.ShouldContain("Lot Number"); + _headers.ShouldContain("Item Number"); + _headers.ShouldContain("Planning Family"); + _headers.ShouldContain("Stocking Type"); + _headers.ShouldContain("Order Quantity"); + _headers.ShouldContain("Held Quantity"); + _headers.ShouldContain("Scrapped Quantity"); + _headers.ShouldContain("Shipped Quantity"); + _headers.ShouldContain("Operation Step Branch Code"); + _headers.ShouldContain("Operation Step"); + _headers.ShouldContain("Operation Step Description"); + _headers.ShouldContain("Function Operation Description"); + _headers.ShouldContain("Operation Step Update Timestamp"); + _headers.ShouldContain("Status Code"); + _headers.ShouldContain("Status Description"); + _headers.ShouldContain("Status Update Timestamp"); + _headers.ShouldContain("Inclusion Reason"); + } + + [Fact] + public void ColumnOrder_MatchesSpec() + { + _headers[0].ShouldBe("Work Order Number"); + _headers[1].ShouldBe("Work Order Branch Code"); + _headers[2].ShouldBe("Lot Number"); + _headers[3].ShouldBe("Item Number"); + _headers[4].ShouldBe("Planning Family"); + _headers[5].ShouldBe("Stocking Type"); + _headers[6].ShouldBe("Order Quantity"); + _headers[7].ShouldBe("Held Quantity"); + _headers[8].ShouldBe("Scrapped Quantity"); + _headers[9].ShouldBe("Shipped Quantity"); + _headers[10].ShouldBe("Operation Step Branch Code"); + _headers[11].ShouldBe("Operation Step"); + _headers[12].ShouldBe("Operation Step Description"); + _headers[13].ShouldBe("Function Operation Description"); + _headers[14].ShouldBe("Operation Step Update Timestamp"); + _headers[15].ShouldBe("Status Code"); + _headers[16].ShouldBe("Status Description"); + _headers[17].ShouldBe("Status Update Timestamp"); + _headers[18].ShouldBe("Inclusion Reason"); + } + + [Fact] + public void TableStyle_IsLight18() + { + var table = _sheet.Tables.First(); + table.Theme.ShouldBe(XLTableTheme.TableStyleLight18); + } + + [Fact] + public void DataRow_ContainsExpectedValues() + { + _sheet.Cell(2, 1).Value.GetNumber().ShouldBe(12345); + _sheet.Cell(2, 3).Value.GetText().ShouldBe("LOT-001"); + _sheet.Cell(2, 4).Value.GetText().ShouldBe("ITEM-001"); + } +} +``` + +**Step 2: Build and run tests** + +```bash +dotnet build tests/JdeScoping.ExcelIO.Tests +dotnet test tests/JdeScoping.ExcelIO.Tests --filter "FullyQualifiedName~SearchResultsSheetTests" --verbosity normal +``` + +--- + +## Task 5: Create MisInfoSheetTests + +**Files:** +- Create: `tests/JdeScoping.ExcelIO.Tests/Integration/MisInfoSheetTests.cs` + +**Step 1: Write MisInfoSheetTests.cs** + +```csharp +using ClosedXML.Excel; +using JdeScoping.ExcelIO.Tests.Fixtures; +using Shouldly; +using Xunit; + +namespace JdeScoping.ExcelIO.Tests.Integration; + +public class MisInfoSheetTests : IClassFixture +{ + private readonly XLWorkbook _workbook; + private readonly IXLWorksheet _sheet; + private readonly List _headers; + + public MisInfoSheetTests(WithMisDataFixture fixture) + { + _workbook = fixture.Workbook; + _sheet = _workbook.Worksheet("MIS Info"); + _headers = ExcelTestHelpers.GetHeadersFromSheet(_sheet); + } + + [Fact] + public void SheetCount_IsFour() + { + _workbook.Worksheets.Count.ShouldBe(4); + } + + [Fact] + public void MisInfoSheet_Exists() + { + _workbook.Worksheets.TryGetWorksheet("MIS Info", out _).ShouldBeTrue(); + } + + [Fact] + public void ColumnCount_Is19() + { + _headers.Count.ShouldBe(19); + } + + [Fact] + public void ColumnHeaders_MatchExpected() + { + _headers.ShouldContain("Item Number"); + _headers.ShouldContain("MIS Job Step Sequence Number"); + _headers.ShouldContain("MIS Number"); + _headers.ShouldContain("MIS Revision"); + _headers.ShouldContain("Item Description"); + _headers.ShouldContain("MIS Release Status"); + _headers.ShouldContain("MIS Release Date"); + _headers.ShouldContain("Branch Code"); + _headers.ShouldContain("Job Step Sequence Number"); + _headers.ShouldContain("Matched Sequence Number"); + _headers.ShouldContain("Matched to F3112Z1?"); + _headers.ShouldContain("Matched to F3003?"); + _headers.ShouldContain("Function Operation Description"); + _headers.ShouldContain("Char Number"); + _headers.ShouldContain("Test Description"); + _headers.ShouldContain("Sampling Type"); + _headers.ShouldContain("Sampling Value"); + _headers.ShouldContain("Tools & Gauges"); + _headers.ShouldContain("Work Instructions"); + } + + [Fact] + public void ColumnOrder_MatchesSpec() + { + _headers[0].ShouldBe("Item Number"); + _headers[1].ShouldBe("MIS Job Step Sequence Number"); + _headers[2].ShouldBe("MIS Number"); + _headers[3].ShouldBe("MIS Revision"); + _headers[4].ShouldBe("Item Description"); + _headers[5].ShouldBe("MIS Release Status"); + _headers[6].ShouldBe("MIS Release Date"); + _headers[7].ShouldBe("Branch Code"); + _headers[8].ShouldBe("Job Step Sequence Number"); + _headers[9].ShouldBe("Matched Sequence Number"); + _headers[10].ShouldBe("Matched to F3112Z1?"); + _headers[11].ShouldBe("Matched to F3003?"); + _headers[12].ShouldBe("Function Operation Description"); + _headers[13].ShouldBe("Char Number"); + _headers[14].ShouldBe("Test Description"); + _headers[15].ShouldBe("Sampling Type"); + _headers[16].ShouldBe("Sampling Value"); + _headers[17].ShouldBe("Tools & Gauges"); + _headers[18].ShouldBe("Work Instructions"); + } + + [Fact] + public void TableStyle_IsLight18() + { + var table = _sheet.Tables.First(); + table.Theme.ShouldBe(XLTableTheme.TableStyleLight18); + } + + [Fact] + public void Protection_IsEnabled() + { + _sheet.Protection.IsProtected.ShouldBeTrue(); + } + + [Fact] + public void DataRow_ContainsExpectedValues() + { + _sheet.Cell(2, 1).Value.GetText().ShouldBe("ITEM-001"); + _sheet.Cell(2, 3).Value.GetText().ShouldBe("MIS-001"); + } + + [Fact] + public void TestDescriptionColumn_IsWrapped() + { + var colIndex = _headers.IndexOf("Test Description") + 1; + _sheet.Column(colIndex).Width.ShouldBe(65); + _sheet.Column(colIndex).Style.Alignment.WrapText.ShouldBeTrue(); + } + + [Fact] + public void ToolsGaugesColumn_IsWrapped() + { + var colIndex = _headers.IndexOf("Tools & Gauges") + 1; + _sheet.Column(colIndex).Width.ShouldBe(65); + _sheet.Column(colIndex).Style.Alignment.WrapText.ShouldBeTrue(); + } + + [Fact] + public void WorkInstructionsColumn_IsWrapped() + { + var colIndex = _headers.IndexOf("Work Instructions") + 1; + _sheet.Column(colIndex).Width.ShouldBe(65); + _sheet.Column(colIndex).Style.Alignment.WrapText.ShouldBeTrue(); + } +} +``` + +**Step 2: Build and run tests** + +```bash +dotnet build tests/JdeScoping.ExcelIO.Tests +dotnet test tests/JdeScoping.ExcelIO.Tests --filter "FullyQualifiedName~MisInfoSheetTests" --verbosity normal +``` + +--- + +## Task 6: Create InvestigationSheetTests + +**Files:** +- Create: `tests/JdeScoping.ExcelIO.Tests/Integration/InvestigationSheetTests.cs` + +**Step 1: Write InvestigationSheetTests.cs** + +```csharp +using ClosedXML.Excel; +using JdeScoping.ExcelIO.Tests.Fixtures; +using Shouldly; +using Xunit; + +namespace JdeScoping.ExcelIO.Tests.Integration; + +public class InvestigationSheetTests : IClassFixture +{ + private readonly XLWorkbook _workbook; + private readonly IXLWorksheet _sheet; + private readonly List _headers; + + public InvestigationSheetTests(WithMisDataFixture fixture) + { + _workbook = fixture.Workbook; + _sheet = _workbook.Worksheet("Investigation"); + _headers = ExcelTestHelpers.GetHeadersFromSheet(_sheet); + } + + [Fact] + public void InvestigationSheet_Exists() + { + _workbook.Worksheets.TryGetWorksheet("Investigation", out _).ShouldBeTrue(); + } + + [Fact] + public void ColumnCount_Is12() + { + _headers.Count.ShouldBe(12); + } + + [Fact] + public void ColumnHeaders_MatchExpected() + { + _headers.ShouldContain("Work Center Code"); + _headers.ShouldContain("Work Order Number"); + _headers.ShouldContain("Work Order Start Date"); + _headers.ShouldContain("Job Step Number"); + _headers.ShouldContain("Function Operation Description"); + _headers.ShouldContain("Job Step End Date"); + _headers.ShouldContain("Function Code"); + _headers.ShouldContain("Was Job Step Added?"); + _headers.ShouldContain("Matched Job Step Number"); + _headers.ShouldContain("Item Number"); + _headers.ShouldContain("Item Description"); + _headers.ShouldContain("Routing Type"); + } + + [Fact] + public void ColumnOrder_MatchesSpec() + { + _headers[0].ShouldBe("Work Center Code"); + _headers[1].ShouldBe("Work Order Number"); + _headers[2].ShouldBe("Work Order Start Date"); + _headers[3].ShouldBe("Job Step Number"); + _headers[4].ShouldBe("Function Operation Description"); + _headers[5].ShouldBe("Job Step End Date"); + _headers[6].ShouldBe("Function Code"); + _headers[7].ShouldBe("Was Job Step Added?"); + _headers[8].ShouldBe("Matched Job Step Number"); + _headers[9].ShouldBe("Item Number"); + _headers[10].ShouldBe("Item Description"); + _headers[11].ShouldBe("Routing Type"); + } + + [Fact] + public void TableStyle_IsLight18() + { + var table = _sheet.Tables.First(); + table.Theme.ShouldBe(XLTableTheme.TableStyleLight18); + } + + [Fact] + public void Protection_IsEnabled() + { + _sheet.Protection.IsProtected.ShouldBeTrue(); + } + + [Fact] + public void DataRow_ContainsExpectedValues() + { + _sheet.Cell(2, 2).Value.GetNumber().ShouldBe(12345); + _sheet.Cell(2, 10).Value.GetText().ShouldBe("ITEM-001"); + } +} +``` + +**Step 2: Build and run tests** + +```bash +dotnet build tests/JdeScoping.ExcelIO.Tests +dotnet test tests/JdeScoping.ExcelIO.Tests --filter "FullyQualifiedName~InvestigationSheetTests" --verbosity normal +``` + +--- + +## Task 7: Create ProtectionAndStyleTests + +**Files:** +- Create: `tests/JdeScoping.ExcelIO.Tests/Integration/ProtectionAndStyleTests.cs` + +**Step 1: Write ProtectionAndStyleTests.cs** + +```csharp +using ClosedXML.Excel; +using JdeScoping.ExcelIO.Tests.Fixtures; +using Shouldly; +using Xunit; + +namespace JdeScoping.ExcelIO.Tests.Integration; + +public class ProtectionAndStyleTests : IClassFixture +{ + private readonly XLWorkbook _workbook; + + public ProtectionAndStyleTests(WithMisDataFixture fixture) + { + _workbook = fixture.Workbook; + } + + [Fact] + public void AllDataSheets_AreProtected() + { + _workbook.Worksheet("Search Results").Protection.IsProtected.ShouldBeTrue(); + _workbook.Worksheet("MIS Info").Protection.IsProtected.ShouldBeTrue(); + _workbook.Worksheet("Investigation").Protection.IsProtected.ShouldBeTrue(); + } + + [Fact] + public void Protection_AllowsFiltering() + { + var sheet = _workbook.Worksheet("Search Results"); + sheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.AutoFilter).ShouldBeTrue(); + } + + [Fact] + public void Protection_AllowsSorting() + { + var sheet = _workbook.Worksheet("Search Results"); + sheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.Sort).ShouldBeTrue(); + } + + [Fact] + public void Protection_AllowsFormatting() + { + var sheet = _workbook.Worksheet("Search Results"); + sheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.FormatCells).ShouldBeTrue(); + sheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.FormatColumns).ShouldBeTrue(); + sheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.FormatRows).ShouldBeTrue(); + } + + [Fact] + public void AllTables_UseLight18Style() + { + _workbook.Worksheet("Search Results").Tables.First().Theme.ShouldBe(XLTableTheme.TableStyleLight18); + _workbook.Worksheet("MIS Info").Tables.First().Theme.ShouldBe(XLTableTheme.TableStyleLight18); + _workbook.Worksheet("Investigation").Tables.First().Theme.ShouldBe(XLTableTheme.TableStyleLight18); + } + + [Fact] + public void HeaderCells_HaveCorrectFormatting() + { + var sheet = _workbook.Worksheet("Search Criteria"); + var headerCell = sheet.Cell(1, 1); + headerCell.Style.Font.Bold.ShouldBeTrue(); + headerCell.Style.Fill.BackgroundColor.ShouldBe(XLColor.Gainsboro); + headerCell.Style.Alignment.Horizontal.ShouldBe(XLAlignmentHorizontalValues.Center); + } + + [Fact] + public void CriteriaTimestamp_MatchesLegacyFormat() + { + var sheet = _workbook.Worksheet("Search Criteria"); + var timestamp = sheet.Cell(4, 2).Value.GetText(); + timestamp.ShouldContain("Jan 15, 2024"); + timestamp.ShouldContain("02:30:45"); + timestamp.ShouldContain("EST"); + } +} +``` + +**Step 2: Build and run tests** + +```bash +dotnet build tests/JdeScoping.ExcelIO.Tests +dotnet test tests/JdeScoping.ExcelIO.Tests --filter "FullyQualifiedName~ProtectionAndStyleTests" --verbosity normal +``` + +--- + +## Task 8: Create LegacyFormatTests and LargeDataSetTests + +**Files:** +- Create: `tests/JdeScoping.ExcelIO.Tests/Integration/LegacyFormatTests.cs` +- Create: `tests/JdeScoping.ExcelIO.Tests/Integration/LargeDataSetTests.cs` + +**Step 1: Write LegacyFormatTests.cs** + +```csharp +using Shouldly; +using Xunit; +using ExcelFormats = JdeScoping.ExcelIO.Formatting.ExcelFormats; + +namespace JdeScoping.ExcelIO.Tests.Integration; + +/// +/// Tests for ExcelFormats constants - no workbook generation needed. +/// +public class LegacyFormatTests +{ + [Fact] + public void TimestampFormat_MatchesLegacy() + { + ExcelFormats.TimestampFormat.ShouldBe("[$-409]m/d/yy h:mm AM/PM;@"); + } + + [Fact] + public void DateFormat_MatchesLegacyPattern() + { + ExcelFormats.DateFormat.ShouldContain("MM/dd/yyyy"); + } + + [Fact] + public void WrappedColumnWidth_MatchesLegacy() + { + ExcelFormats.WrappedColumnWidth.ShouldBe(65); + } + + [Fact] + public void CriteriaPaddingFactor_MatchesLegacy() + { + ExcelFormats.CriteriaPaddingFactor.ShouldBe(1.15); + } + + [Fact] + public void DataPaddingFactor_MatchesLegacy() + { + ExcelFormats.DataPaddingFactor.ShouldBe(1.30); + } +} +``` + +**Step 2: Write LargeDataSetTests.cs** + +```csharp +using ClosedXML.Excel; +using JdeScoping.ExcelIO.Tests.Fixtures; +using Shouldly; +using Xunit; + +namespace JdeScoping.ExcelIO.Tests.Integration; + +public class LargeDataSetTests : IClassFixture +{ + private readonly XLWorkbook _workbook; + + public LargeDataSetTests(LargeDataSetFixture fixture) + { + _workbook = fixture.Workbook; + } + + [Fact] + public void TableRowCount_Is1001() + { + var sheet = _workbook.Worksheet("Search Results"); + var table = sheet.Tables.First(); + table.RowCount().ShouldBe(1001); // 1 header + 1000 data rows + } +} +``` + +**Step 3: Build and run tests** + +```bash +dotnet build tests/JdeScoping.ExcelIO.Tests +dotnet test tests/JdeScoping.ExcelIO.Tests --filter "FullyQualifiedName~LegacyFormatTests|FullyQualifiedName~LargeDataSetTests" --verbosity normal +``` + +--- + +## Task 9: Delete Old Integration Test Files + +**Files:** +- Delete: `tests/JdeScoping.ExcelIO.Tests/ExcelExportIntegrationTests.cs` +- Delete: `tests/JdeScoping.ExcelIO.Tests/LegacyComparisonTests.cs` + +**Step 1: Delete old files** + +```bash +rm tests/JdeScoping.ExcelIO.Tests/ExcelExportIntegrationTests.cs +rm tests/JdeScoping.ExcelIO.Tests/LegacyComparisonTests.cs +``` + +**Step 2: Build and verify all tests pass** + +```bash +dotnet build tests/JdeScoping.ExcelIO.Tests +dotnet test tests/JdeScoping.ExcelIO.Tests --verbosity normal +``` + +--- + +## Task 10: Run Full Test Suite and Measure Performance + +**Step 1: Run full test suite with timing** + +```bash +time dotnet test tests/JdeScoping.ExcelIO.Tests --verbosity minimal +``` + +**Step 2: Verify test count matches expected** + +Expected: ~103 tests (same as before, just reorganized) + +**Step 3: Commit changes** + +```bash +git add -A +git commit -m "perf: optimize ExcelIO tests with fixture-based consolidation + +- Add WorkbookFixtureBase and 4 concrete fixtures +- Reorganize integration tests to use IClassFixture +- Reduce workbook generations from ~42 to 4 +- Delete redundant ExcelExportIntegrationTests.cs +- Delete redundant LegacyComparisonTests.cs +- Test coverage unchanged, runtime reduced ~75-80%" +``` + +--- + +## Summary + +| Task | Description | +|------|-------------| +| 1 | Create fixture base class and helpers | +| 2 | Create 4 concrete fixture classes | +| 3 | Create MinimalSearchTests (5 tests) | +| 4 | Create SearchResultsSheetTests (5 tests) | +| 5 | Create MisInfoSheetTests (12 tests) | +| 6 | Create InvestigationSheetTests (7 tests) | +| 7 | Create ProtectionAndStyleTests (7 tests) | +| 8 | Create LegacyFormatTests + LargeDataSetTests (6 tests) | +| 9 | Delete old integration test files | +| 10 | Run full test suite or measure performance | diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/ExcelExportIntegrationTests.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/ExcelExportIntegrationTests.cs deleted file mode 100644 index 6f4d9e9..0000000 --- a/NEW/tests/JdeScoping.ExcelIO.Tests/ExcelExportIntegrationTests.cs +++ /dev/null @@ -1,682 +0,0 @@ -using ClosedXML.Excel; -using JdeScoping.ExcelIO.Options; -using JdeScoping.ExcelIO.Generators; -using JdeScoping.ExcelIO.Mapping; -using JdeScoping.ExcelIO.Mapping.Maps; -using JdeScoping.ExcelIO.Models.Reporting; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using NSubstitute; -using Shouldly; -using Xunit; - -using SearchResult = JdeScoping.Core.Models.SearchResults.SearchResult; -using MisSearchResult = JdeScoping.Core.Models.SearchResults.MisSearchResult; -using MisNonMatchSearchResult = JdeScoping.Core.Models.SearchResults.MisNonMatchSearchResult; - -namespace JdeScoping.ExcelIO.Tests; - -/// -/// Integration tests that generate actual .xlsx files and verify structure with ClosedXML. -/// -public class ExcelExportIntegrationTests -{ - private readonly ExcelExportService _service; - private readonly ILogger _logger; - private readonly IOptions _options; - - public ExcelExportIntegrationTests() - { - _logger = Substitute.For>(); - _options = Microsoft.Extensions.Options.Options.Create(new ExcelExportOptions - { - CriteriaSheetPassword = "TestCriteriaPass", - DataSheetPassword = "TestDataPass" - }); - - var registry = CreateTestRegistry(); - var tableWriter = new FluentTableWriter(registry); - var criteriaGenerator = new CriteriaSheetGenerator(_options, tableWriter); - - _service = new ExcelExportService(_logger, _options, criteriaGenerator, tableWriter, registry); - } - - private static ExcelMapRegistry CreateTestRegistry() - { - var registry = new ExcelMapRegistry(); - - // Search result maps - registry.Register(new SearchResultMap()); - registry.Register(new MisSearchResultMap()); - registry.Register(new MisNonMatchSearchResultMap()); - - // Filter entry maps - registry.Register(new TimespanFilterMap()); - registry.Register(new WorkOrderFilterEntryMap()); - registry.Register(new ItemNumberFilterEntryMap()); - registry.Register(new ProfitCenterFilterEntryMap()); - registry.Register(new WorkCenterFilterEntryMap()); - registry.Register(new OperatorFilterEntryMap()); - registry.Register(new ComponentLotFilterEntryMap()); - registry.Register(new ItemOperationMisFilterEntryMap()); - - return registry; - } - - #region Sheet Count Tests - - [Fact] - public async Task GenerateAsync_MinimalSearch_HasTwoSheets() - { - var search = CreateMinimalSearchModel(); - - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - workbook.Worksheets.Count.ShouldBe(2); - } - - [Fact] - public async Task GenerateAsync_WithMisData_HasFourSheets() - { - var search = CreateSearchModelWithMisData(); - - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - workbook.Worksheets.Count.ShouldBe(4); - } - - #endregion - - #region Sheet Name Tests - - [Fact] - public async Task GenerateAsync_CreatesSearchCriteriaSheet() - { - var search = CreateMinimalSearchModel(); - - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - workbook.Worksheets.TryGetWorksheet("Search Criteria", out _).ShouldBeTrue(); - } - - [Fact] - public async Task GenerateAsync_CreatesSearchResultsSheet() - { - var search = CreateMinimalSearchModel(); - - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - workbook.Worksheets.TryGetWorksheet("Search Results", out _).ShouldBeTrue(); - } - - [Fact] - public async Task GenerateAsync_WithMisData_CreatesMisInfoSheet() - { - var search = CreateSearchModelWithMisData(); - - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - workbook.Worksheets.TryGetWorksheet("MIS Info", out _).ShouldBeTrue(); - } - - [Fact] - public async Task GenerateAsync_WithMisData_CreatesInvestigationSheet() - { - var search = CreateSearchModelWithMisData(); - - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - workbook.Worksheets.TryGetWorksheet("Investigation", out _).ShouldBeTrue(); - } - - #endregion - - #region Column Header Tests - Search Results - - [Fact] - public async Task GenerateAsync_SearchResultsSheet_Has19ColumnHeaders() - { - var search = CreateMinimalSearchModel(); - search.Results.Add(CreateSampleSearchResult()); - - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - var resultsSheet = workbook.Worksheet("Search Results"); - - // Count non-empty cells in first row to get column count - var headers = GetHeadersFromSheet(resultsSheet); - headers.Count.ShouldBe(19); - } - - [Fact] - public async Task GenerateAsync_SearchResultsSheet_HasCorrectColumnHeaders() - { - var search = CreateMinimalSearchModel(); - search.Results.Add(CreateSampleSearchResult()); - - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - var resultsSheet = workbook.Worksheet("Search Results"); - - var headers = GetHeadersFromSheet(resultsSheet); - - headers.ShouldContain("Work Order Number"); - headers.ShouldContain("Work Order Branch Code"); - headers.ShouldContain("Lot Number"); - headers.ShouldContain("Item Number"); - headers.ShouldContain("Planning Family"); - headers.ShouldContain("Stocking Type"); - headers.ShouldContain("Order Quantity"); - headers.ShouldContain("Held Quantity"); - headers.ShouldContain("Scrapped Quantity"); - headers.ShouldContain("Shipped Quantity"); - headers.ShouldContain("Operation Step Branch Code"); - headers.ShouldContain("Operation Step"); - headers.ShouldContain("Operation Step Description"); - headers.ShouldContain("Function Operation Description"); - headers.ShouldContain("Operation Step Update Timestamp"); - headers.ShouldContain("Status Code"); - headers.ShouldContain("Status Description"); - headers.ShouldContain("Status Update Timestamp"); - headers.ShouldContain("Inclusion Reason"); - } - - #endregion - - #region Column Header Tests - MIS Info - - [Fact] - public async Task GenerateAsync_MisInfoSheet_Has19ColumnHeaders() - { - var search = CreateSearchModelWithMisData(); - - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - var misSheet = workbook.Worksheet("MIS Info"); - - var headers = GetHeadersFromSheet(misSheet); - headers.Count.ShouldBe(19); - } - - [Fact] - public async Task GenerateAsync_MisInfoSheet_HasCorrectColumnHeaders() - { - var search = CreateSearchModelWithMisData(); - - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - var misSheet = workbook.Worksheet("MIS Info"); - - var headers = GetHeadersFromSheet(misSheet); - - headers.ShouldContain("Item Number"); - headers.ShouldContain("MIS Job Step Sequence Number"); - headers.ShouldContain("MIS Number"); - headers.ShouldContain("MIS Revision"); - headers.ShouldContain("Item Description"); - headers.ShouldContain("MIS Release Status"); - headers.ShouldContain("MIS Release Date"); - headers.ShouldContain("Branch Code"); - headers.ShouldContain("Job Step Sequence Number"); - headers.ShouldContain("Matched Sequence Number"); - headers.ShouldContain("Matched to F3112Z1?"); - headers.ShouldContain("Matched to F3003?"); - headers.ShouldContain("Function Operation Description"); - headers.ShouldContain("Char Number"); - headers.ShouldContain("Test Description"); - headers.ShouldContain("Sampling Type"); - headers.ShouldContain("Sampling Value"); - headers.ShouldContain("Tools & Gauges"); - headers.ShouldContain("Work Instructions"); - } - - #endregion - - #region Column Header Tests - Investigation - - [Fact] - public async Task GenerateAsync_InvestigationSheet_Has12ColumnHeaders() - { - var search = CreateSearchModelWithMisData(); - - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - var investigationSheet = workbook.Worksheet("Investigation"); - - var headers = GetHeadersFromSheet(investigationSheet); - headers.Count.ShouldBe(12); - } - - [Fact] - public async Task GenerateAsync_InvestigationSheet_HasCorrectColumnHeaders() - { - var search = CreateSearchModelWithMisData(); - - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - var investigationSheet = workbook.Worksheet("Investigation"); - - var headers = GetHeadersFromSheet(investigationSheet); - - headers.ShouldContain("Work Center Code"); - headers.ShouldContain("Work Order Number"); - headers.ShouldContain("Work Order Start Date"); - headers.ShouldContain("Job Step Number"); - headers.ShouldContain("Function Operation Description"); - headers.ShouldContain("Job Step End Date"); - headers.ShouldContain("Function Code"); - headers.ShouldContain("Was Job Step Added?"); - headers.ShouldContain("Matched Job Step Number"); - headers.ShouldContain("Item Number"); - headers.ShouldContain("Item Description"); - headers.ShouldContain("Routing Type"); - } - - #endregion - - #region Table Style Tests - - [Fact] - public async Task GenerateAsync_SearchResultsTable_UsesLight18Style() - { - var search = CreateMinimalSearchModel(); - search.Results.Add(CreateSampleSearchResult()); - - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - var resultsSheet = workbook.Worksheet("Search Results"); - - var table = resultsSheet.Tables.First(); - table.Theme.ShouldBe(XLTableTheme.TableStyleLight18); - } - - [Fact] - public async Task GenerateAsync_MisInfoTable_UsesLight18Style() - { - var search = CreateSearchModelWithMisData(); - - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - var misSheet = workbook.Worksheet("MIS Info"); - - var table = misSheet.Tables.First(); - table.Theme.ShouldBe(XLTableTheme.TableStyleLight18); - } - - [Fact] - public async Task GenerateAsync_InvestigationTable_UsesLight18Style() - { - var search = CreateSearchModelWithMisData(); - - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - var investigationSheet = workbook.Worksheet("Investigation"); - - var table = investigationSheet.Tables.First(); - table.Theme.ShouldBe(XLTableTheme.TableStyleLight18); - } - - #endregion - - #region Protection Tests - - [Fact] - public async Task GenerateAsync_SearchCriteriaSheet_IsProtected() - { - var search = CreateMinimalSearchModel(); - - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - var criteriaSheet = workbook.Worksheet("Search Criteria"); - - criteriaSheet.Protection.IsProtected.ShouldBeTrue(); - } - - [Fact] - public async Task GenerateAsync_SearchResultsSheet_IsProtected() - { - var search = CreateMinimalSearchModel(); - - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - var resultsSheet = workbook.Worksheet("Search Results"); - - resultsSheet.Protection.IsProtected.ShouldBeTrue(); - } - - [Fact] - public async Task GenerateAsync_MisInfoSheet_IsProtected() - { - var search = CreateSearchModelWithMisData(); - - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - var misSheet = workbook.Worksheet("MIS Info"); - - misSheet.Protection.IsProtected.ShouldBeTrue(); - } - - [Fact] - public async Task GenerateAsync_InvestigationSheet_IsProtected() - { - var search = CreateSearchModelWithMisData(); - - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - var investigationSheet = workbook.Worksheet("Investigation"); - - investigationSheet.Protection.IsProtected.ShouldBeTrue(); - } - - #endregion - - #region Data Validation Tests - - [Fact] - public async Task GenerateAsync_SearchResults_ContainsCorrectData() - { - var search = CreateMinimalSearchModel(); - var searchResult = new SearchResult - { - WorkOrderNumber = 99999, - ItemNumber = "TEST-ITEM-001", - LotNumber = "LOT-999", - Flagged = true - }; - search.Results.Add(searchResult); - - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - var resultsSheet = workbook.Worksheet("Search Results"); - - // Data should be in row 2 (after header) - resultsSheet.Cell(2, 1).Value.GetNumber().ShouldBe(99999); - resultsSheet.Cell(2, 3).Value.GetText().ShouldBe("LOT-999"); - resultsSheet.Cell(2, 4).Value.GetText().ShouldBe("TEST-ITEM-001"); - } - - [Fact] - public async Task GenerateAsync_MisInfo_ContainsCorrectData() - { - var search = CreateMinimalSearchModel(); - search.ExtractMisData = true; - search.MisResults = [ - new MisSearchResult - { - ItemNumber = "MIS-ITEM-001", - MisNumber = "MIS-12345" - } - ]; - search.MisNonMatchResults = []; - - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - var misSheet = workbook.Worksheet("MIS Info"); - - // Data should be in row 2 (after header) - misSheet.Cell(2, 1).Value.GetText().ShouldBe("MIS-ITEM-001"); - misSheet.Cell(2, 3).Value.GetText().ShouldBe("MIS-12345"); - } - - [Fact] - public async Task GenerateAsync_Investigation_ContainsCorrectData() - { - var search = CreateMinimalSearchModel(); - search.ExtractMisData = true; - search.MisResults = []; - search.MisNonMatchResults = [ - new MisNonMatchSearchResult - { - WorkOrderNumber = 77777, - ItemNumber = "INV-ITEM-001" - } - ]; - - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - var investigationSheet = workbook.Worksheet("Investigation"); - - // Data should be in row 2 (after header) - investigationSheet.Cell(2, 2).Value.GetNumber().ShouldBe(77777); - investigationSheet.Cell(2, 10).Value.GetText().ShouldBe("INV-ITEM-001"); - } - - #endregion - - #region Wrapped Column Tests - - [Fact] - public async Task GenerateAsync_MisInfo_TestDescriptionColumn_IsWrapped() - { - var search = CreateSearchModelWithMisData(); - - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - var misSheet = workbook.Worksheet("MIS Info"); - - // Find the Test Description column (should be column 15) - var headers = GetHeadersFromSheet(misSheet); - var testDescColIndex = headers.IndexOf("Test Description") + 1; - - misSheet.Column(testDescColIndex).Width.ShouldBe(65); - misSheet.Column(testDescColIndex).Style.Alignment.WrapText.ShouldBeTrue(); - } - - [Fact] - public async Task GenerateAsync_MisInfo_ToolsGaugesColumn_IsWrapped() - { - var search = CreateSearchModelWithMisData(); - - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - var misSheet = workbook.Worksheet("MIS Info"); - - var headers = GetHeadersFromSheet(misSheet); - var toolsGaugesColIndex = headers.IndexOf("Tools & Gauges") + 1; - - misSheet.Column(toolsGaugesColIndex).Width.ShouldBe(65); - misSheet.Column(toolsGaugesColIndex).Style.Alignment.WrapText.ShouldBeTrue(); - } - - [Fact] - public async Task GenerateAsync_MisInfo_WorkInstructionsColumn_IsWrapped() - { - var search = CreateSearchModelWithMisData(); - - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - var misSheet = workbook.Worksheet("MIS Info"); - - var headers = GetHeadersFromSheet(misSheet); - var workInstructionsColIndex = headers.IndexOf("Work Instructions") + 1; - - misSheet.Column(workInstructionsColIndex).Width.ShouldBe(65); - misSheet.Column(workInstructionsColIndex).Style.Alignment.WrapText.ShouldBeTrue(); - } - - #endregion - - #region Large Data Set Tests - - [Fact] - public async Task GenerateAsync_LargeDataSet_GeneratesSuccessfully() - { - var search = CreateMinimalSearchModel(); - - // Add 1000 search results - for (int i = 0; i < 1000; i++) - { - search.Results.Add(new SearchResult - { - WorkOrderNumber = 10000 + i, - ItemNumber = $"ITEM-{i:D5}", - LotNumber = $"LOT-{i:D5}", - Flagged = true - }); - } - - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - var resultsSheet = workbook.Worksheet("Search Results"); - - // Should have 1001 rows (1 header + 1000 data rows) - var table = resultsSheet.Tables.First(); - table.RowCount().ShouldBe(1001); - } - - #endregion - - #region Helper Methods - - private static List GetHeadersFromSheet(IXLWorksheet sheet) - { - var headers = new List(); - var col = 1; - while (!sheet.Cell(1, col).IsEmpty()) - { - headers.Add(sheet.Cell(1, col).Value.GetText()); - col++; - } - return headers; - } - - private static SearchModel CreateMinimalSearchModel() - { - return new SearchModel - { - Id = 1, - Name = "Integration Test Search", - UserName = "testuser", - SubmitDt = DateTime.Now.AddHours(-1), - StartDt = DateTime.Now.AddMinutes(-30), - EndDt = DateTime.Now, - ExtractMisData = false, - Results = [] - }; - } - - private static SearchModel CreateSearchModelWithMisData() - { - return new SearchModel - { - Id = 1, - Name = "Integration Test Search with MIS", - UserName = "testuser", - SubmitDt = DateTime.Now.AddHours(-1), - StartDt = DateTime.Now.AddMinutes(-30), - EndDt = DateTime.Now, - ExtractMisData = true, - Results = [ - CreateSampleSearchResult() - ], - MisResults = [ - new MisSearchResult - { - ItemNumber = "ITEM-001", - MisNumber = "MIS-001", - RevId = "A", - ItemDescription = "Test Item Description", - Status = "Released", - BranchCode = "001", - JobStepSequenceNumber = 10, - TestDescription = "Sample test description", - ToolsGauges = "Sample tools and gauges", - WorkInstructions = "Sample work instructions" - } - ], - MisNonMatchResults = [ - new MisNonMatchSearchResult - { - WorkOrderNumber = 12345, - ItemNumber = "ITEM-001", - WorkCenterCode = "WC01", - WorkOrderStartDate = DateTime.Now.AddDays(-7), - JobStepNumber = 10, - JobStepDescription = "Test job step", - FunctionCode = "FC01", - RoutingType = "M" - } - ] - }; - } - - private static SearchResult CreateSampleSearchResult() - { - return new SearchResult - { - WorkOrderNumber = 12345, - WorkOrderBranchCode = "001", - LotNumber = "LOT-001", - ItemNumber = "ITEM-001", - PlanningFamily = "PF01", - StockingType = "M", - OrderQuantity = 100, - HeldQuantity = 0, - ScrappedQuantity = 0, - ShippedQuantity = 50, - StepBranchCode = "001", - StepNumber = 10, - StepDescription = "Assembly", - FunctionOperationDescription = "Main assembly operation", - StepUpdateDt = DateTime.Now.AddDays(-1), - StatusCode = "50", - StatusDescription = "In Progress", - StatusUpdateDt = DateTime.Now.AddDays(-1), - Flagged = true - }; - } - - #endregion -} diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/Fixtures/ExcelTestHelpers.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/Fixtures/ExcelTestHelpers.cs new file mode 100644 index 0000000..6da7382 --- /dev/null +++ b/NEW/tests/JdeScoping.ExcelIO.Tests/Fixtures/ExcelTestHelpers.cs @@ -0,0 +1,18 @@ +using ClosedXML.Excel; + +namespace JdeScoping.ExcelIO.Tests.Fixtures; + +public static class ExcelTestHelpers +{ + public static List GetHeadersFromSheet(IXLWorksheet sheet) + { + var headers = new List(); + var col = 1; + while (!sheet.Cell(1, col).IsEmpty()) + { + headers.Add(sheet.Cell(1, col).Value.GetText()); + col++; + } + return headers; + } +} diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/Fixtures/LargeDataSetFixture.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/Fixtures/LargeDataSetFixture.cs new file mode 100644 index 0000000..783fa4b --- /dev/null +++ b/NEW/tests/JdeScoping.ExcelIO.Tests/Fixtures/LargeDataSetFixture.cs @@ -0,0 +1,35 @@ +using JdeScoping.ExcelIO.Models.Reporting; +using SearchResult = JdeScoping.Core.Models.SearchResults.SearchResult; + +namespace JdeScoping.ExcelIO.Tests.Fixtures; + +public class LargeDataSetFixture : WorkbookFixtureBase +{ + protected override SearchModel CreateSearchModel() + { + var model = new SearchModel + { + Id = 1, + Name = "Large Data Set Test", + UserName = "testuser", + SubmitDt = new DateTime(2024, 1, 15, 14, 30, 45), + StartDt = new DateTime(2024, 1, 15, 14, 31, 0), + EndDt = new DateTime(2024, 1, 15, 14, 35, 0), + ExtractMisData = false, + Results = [] + }; + + for (int i = 0; i < 1000; i++) + { + model.Results.Add(new SearchResult + { + WorkOrderNumber = 10000 + i, + ItemNumber = $"ITEM-{i:D5}", + LotNumber = $"LOT-{i:D5}", + Flagged = true + }); + } + + return model; + } +} diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/Fixtures/MinimalSearchFixture.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/Fixtures/MinimalSearchFixture.cs new file mode 100644 index 0000000..54ae2e9 --- /dev/null +++ b/NEW/tests/JdeScoping.ExcelIO.Tests/Fixtures/MinimalSearchFixture.cs @@ -0,0 +1,18 @@ +using JdeScoping.ExcelIO.Models.Reporting; + +namespace JdeScoping.ExcelIO.Tests.Fixtures; + +public class MinimalSearchFixture : WorkbookFixtureBase +{ + protected override SearchModel CreateSearchModel() => new() + { + Id = 1, + Name = "Minimal Search Test", + UserName = "testuser", + SubmitDt = new DateTime(2024, 1, 15, 14, 30, 45), + StartDt = new DateTime(2024, 1, 15, 14, 31, 0), + EndDt = new DateTime(2024, 1, 15, 14, 35, 0), + ExtractMisData = false, + Results = [] + }; +} diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/Fixtures/WithMisDataFixture.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/Fixtures/WithMisDataFixture.cs new file mode 100644 index 0000000..1d6b208 --- /dev/null +++ b/NEW/tests/JdeScoping.ExcelIO.Tests/Fixtures/WithMisDataFixture.cs @@ -0,0 +1,87 @@ +using JdeScoping.ExcelIO.Models.Reporting; +using SearchResult = JdeScoping.Core.Models.SearchResults.SearchResult; +using MisSearchResult = JdeScoping.Core.Models.SearchResults.MisSearchResult; +using MisNonMatchSearchResult = JdeScoping.Core.Models.SearchResults.MisNonMatchSearchResult; + +namespace JdeScoping.ExcelIO.Tests.Fixtures; + +public class WithMisDataFixture : WorkbookFixtureBase +{ + protected override SearchModel CreateSearchModel() => new() + { + Id = 1, + Name = "Search With MIS Data", + UserName = "testuser", + SubmitDt = new DateTime(2024, 1, 15, 14, 30, 45), + StartDt = new DateTime(2024, 1, 15, 14, 31, 0), + EndDt = new DateTime(2024, 1, 15, 14, 35, 0), + ExtractMisData = true, + Results = + [ + new SearchResult + { + WorkOrderNumber = 12345, + WorkOrderBranchCode = "001", + LotNumber = "LOT-001", + ItemNumber = "ITEM-001", + PlanningFamily = "PF01", + StockingType = "M", + OrderQuantity = 100, + HeldQuantity = 0, + ScrappedQuantity = 0, + ShippedQuantity = 50, + StepBranchCode = "001", + StepNumber = 10, + StepDescription = "Assembly", + FunctionOperationDescription = "Main assembly", + StepUpdateDt = new DateTime(2024, 1, 14, 10, 0, 0), + StatusCode = "50", + StatusDescription = "In Progress", + Flagged = true + } + ], + MisResults = + [ + new MisSearchResult + { + ItemNumber = "ITEM-001", + SequenceNumber = "010", + MisNumber = "MIS-001", + RevId = "A", + ItemDescription = "Test Item", + Status = "Released", + ReleaseDate = new DateTime(2023, 12, 15), + BranchCode = "001", + JobStepSequenceNumber = 10, + MatchedSequenceNumber = 10, + RoutingMatch = true, + MasterMatch = true, + FunctionOperationDescription = "Assembly operation", + CharNumber = "001", + TestDescription = "Sample test description", + SamplingType = "100%", + SamplingValue = "1", + ToolsGauges = "Gauge A, Gauge B", + WorkInstructions = "Step 1: Do this. Step 2: Do that." + } + ], + MisNonMatchResults = + [ + new MisNonMatchSearchResult + { + WorkCenterCode = "WC01", + WorkOrderNumber = 12345, + WorkOrderStartDate = new DateTime(2024, 1, 8), + JobStepNumber = 10, + JobStepDescription = "Test operation", + JobStepEndDate = new DateTime(2024, 1, 10), + FunctionCode = "FC01", + WasJobStepAdded = false, + MatchedJobStepNumber = 10, + ItemNumber = "ITEM-001", + ItemDescription = "Test Item Description", + RoutingType = "M" + } + ] + }; +} diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/Fixtures/WithResultsFixture.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/Fixtures/WithResultsFixture.cs new file mode 100644 index 0000000..e882c9f --- /dev/null +++ b/NEW/tests/JdeScoping.ExcelIO.Tests/Fixtures/WithResultsFixture.cs @@ -0,0 +1,43 @@ +using JdeScoping.ExcelIO.Models.Reporting; +using SearchResult = JdeScoping.Core.Models.SearchResults.SearchResult; + +namespace JdeScoping.ExcelIO.Tests.Fixtures; + +public class WithResultsFixture : WorkbookFixtureBase +{ + protected override SearchModel CreateSearchModel() => new() + { + Id = 1, + Name = "Search With Results", + UserName = "testuser", + SubmitDt = new DateTime(2024, 1, 15, 14, 30, 45), + StartDt = new DateTime(2024, 1, 15, 14, 31, 0), + EndDt = new DateTime(2024, 1, 15, 14, 35, 0), + ExtractMisData = false, + Results = + [ + new SearchResult + { + WorkOrderNumber = 12345, + WorkOrderBranchCode = "001", + LotNumber = "LOT-001", + ItemNumber = "ITEM-001", + PlanningFamily = "PF01", + StockingType = "M", + OrderQuantity = 100, + HeldQuantity = 0, + ScrappedQuantity = 0, + ShippedQuantity = 50, + StepBranchCode = "001", + StepNumber = 10, + StepDescription = "Assembly", + FunctionOperationDescription = "Main assembly operation", + StepUpdateDt = new DateTime(2024, 1, 14, 10, 0, 0), + StatusCode = "50", + StatusDescription = "In Progress", + StatusUpdateDt = new DateTime(2024, 1, 14, 10, 0, 0), + Flagged = true + } + ] + }; +} diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/Fixtures/WorkbookFixtureBase.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/Fixtures/WorkbookFixtureBase.cs new file mode 100644 index 0000000..e99baf7 --- /dev/null +++ b/NEW/tests/JdeScoping.ExcelIO.Tests/Fixtures/WorkbookFixtureBase.cs @@ -0,0 +1,68 @@ +using ClosedXML.Excel; +using JdeScoping.ExcelIO.Generators; +using JdeScoping.ExcelIO.Mapping; +using JdeScoping.ExcelIO.Mapping.Maps; +using JdeScoping.ExcelIO.Models.Reporting; +using JdeScoping.ExcelIO.Options; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using NSubstitute; + +namespace JdeScoping.ExcelIO.Tests.Fixtures; + +public abstract class WorkbookFixtureBase : IDisposable +{ + public XLWorkbook Workbook { get; } + public SearchModel SearchModel { get; } + + protected abstract SearchModel CreateSearchModel(); + + protected WorkbookFixtureBase() + { + SearchModel = CreateSearchModel(); + var service = CreateExportService(); + var bytes = service.GenerateAsync(SearchModel).GetAwaiter().GetResult(); + Workbook = new XLWorkbook(new MemoryStream(bytes)); + } + + private static ExcelExportService CreateExportService() + { + var logger = Substitute.For>(); + var options = Microsoft.Extensions.Options.Options.Create(new ExcelExportOptions + { + CriteriaSheetPassword = "TestCriteriaPass", + DataSheetPassword = "TestDataPass" + }); + + var registry = CreateTestRegistry(); + var tableWriter = new FluentTableWriter(registry); + var criteriaGenerator = new CriteriaSheetGenerator(options, tableWriter); + + return new ExcelExportService(logger, options, criteriaGenerator, tableWriter, registry); + } + + private static ExcelMapRegistry CreateTestRegistry() + { + var registry = new ExcelMapRegistry(); + + registry.Register(new SearchResultMap()); + registry.Register(new MisSearchResultMap()); + registry.Register(new MisNonMatchSearchResultMap()); + registry.Register(new TimespanFilterMap()); + registry.Register(new WorkOrderFilterEntryMap()); + registry.Register(new ItemNumberFilterEntryMap()); + registry.Register(new ProfitCenterFilterEntryMap()); + registry.Register(new WorkCenterFilterEntryMap()); + registry.Register(new OperatorFilterEntryMap()); + registry.Register(new ComponentLotFilterEntryMap()); + registry.Register(new ItemOperationMisFilterEntryMap()); + + return registry; + } + + public void Dispose() + { + Workbook.Dispose(); + GC.SuppressFinalize(this); + } +} diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/InvestigationSheetTests.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/InvestigationSheetTests.cs new file mode 100644 index 0000000..5d0e600 --- /dev/null +++ b/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/InvestigationSheetTests.cs @@ -0,0 +1,86 @@ +using ClosedXML.Excel; +using JdeScoping.ExcelIO.Tests.Fixtures; +using Shouldly; +using Xunit; + +namespace JdeScoping.ExcelIO.Tests.Integration; + +public class InvestigationSheetTests : IClassFixture +{ + private readonly XLWorkbook _workbook; + private readonly IXLWorksheet _sheet; + private readonly List _headers; + + public InvestigationSheetTests(WithMisDataFixture fixture) + { + _workbook = fixture.Workbook; + _sheet = _workbook.Worksheet("Investigation"); + _headers = ExcelTestHelpers.GetHeadersFromSheet(_sheet); + } + + [Fact] + public void InvestigationSheet_Exists() + { + _workbook.Worksheets.TryGetWorksheet("Investigation", out _).ShouldBeTrue(); + } + + [Fact] + public void ColumnCount_Is12() + { + _headers.Count.ShouldBe(12); + } + + [Fact] + public void ColumnHeaders_MatchExpected() + { + _headers.ShouldContain("Work Center Code"); + _headers.ShouldContain("Work Order Number"); + _headers.ShouldContain("Work Order Start Date"); + _headers.ShouldContain("Job Step Number"); + _headers.ShouldContain("Function Operation Description"); + _headers.ShouldContain("Job Step End Date"); + _headers.ShouldContain("Function Code"); + _headers.ShouldContain("Was Job Step Added?"); + _headers.ShouldContain("Matched Job Step Number"); + _headers.ShouldContain("Item Number"); + _headers.ShouldContain("Item Description"); + _headers.ShouldContain("Routing Type"); + } + + [Fact] + public void ColumnOrder_MatchesSpec() + { + _headers[0].ShouldBe("Work Center Code"); + _headers[1].ShouldBe("Work Order Number"); + _headers[2].ShouldBe("Work Order Start Date"); + _headers[3].ShouldBe("Job Step Number"); + _headers[4].ShouldBe("Function Operation Description"); + _headers[5].ShouldBe("Job Step End Date"); + _headers[6].ShouldBe("Function Code"); + _headers[7].ShouldBe("Was Job Step Added?"); + _headers[8].ShouldBe("Matched Job Step Number"); + _headers[9].ShouldBe("Item Number"); + _headers[10].ShouldBe("Item Description"); + _headers[11].ShouldBe("Routing Type"); + } + + [Fact] + public void TableStyle_IsLight18() + { + var table = _sheet.Tables.First(); + table.Theme.ShouldBe(XLTableTheme.TableStyleLight18); + } + + [Fact] + public void Protection_IsEnabled() + { + _sheet.Protection.IsProtected.ShouldBeTrue(); + } + + [Fact] + public void DataRow_ContainsExpectedValues() + { + _sheet.Cell(2, 2).Value.GetNumber().ShouldBe(12345); + _sheet.Cell(2, 10).Value.GetText().ShouldBe("ITEM-001"); + } +} diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/LargeDataSetTests.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/LargeDataSetTests.cs new file mode 100644 index 0000000..7a50670 --- /dev/null +++ b/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/LargeDataSetTests.cs @@ -0,0 +1,24 @@ +using ClosedXML.Excel; +using JdeScoping.ExcelIO.Tests.Fixtures; +using Shouldly; +using Xunit; + +namespace JdeScoping.ExcelIO.Tests.Integration; + +public class LargeDataSetTests : IClassFixture +{ + private readonly XLWorkbook _workbook; + + public LargeDataSetTests(LargeDataSetFixture fixture) + { + _workbook = fixture.Workbook; + } + + [Fact] + public void TableRowCount_Is1001() + { + var sheet = _workbook.Worksheet("Search Results"); + var table = sheet.Tables.First(); + table.RowCount().ShouldBe(1001); // 1 header + 1000 data rows + } +} diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/LegacyFormatTests.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/LegacyFormatTests.cs new file mode 100644 index 0000000..23d70ad --- /dev/null +++ b/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/LegacyFormatTests.cs @@ -0,0 +1,41 @@ +using Shouldly; +using Xunit; +using ExcelFormats = JdeScoping.ExcelIO.Formatting.ExcelFormats; + +namespace JdeScoping.ExcelIO.Tests.Integration; + +/// +/// Tests for ExcelFormats constants - no workbook generation needed. +/// +public class LegacyFormatTests +{ + [Fact] + public void TimestampFormat_MatchesLegacy() + { + ExcelFormats.TimestampFormat.ShouldBe("[$-409]m/d/yy h:mm AM/PM;@"); + } + + [Fact] + public void DateFormat_MatchesLegacyPattern() + { + ExcelFormats.DateFormat.ShouldContain("MM/dd/yyyy"); + } + + [Fact] + public void WrappedColumnWidth_MatchesLegacy() + { + ExcelFormats.WrappedColumnWidth.ShouldBe(65); + } + + [Fact] + public void CriteriaPaddingFactor_MatchesLegacy() + { + ExcelFormats.CriteriaPaddingFactor.ShouldBe(1.15); + } + + [Fact] + public void DataPaddingFactor_MatchesLegacy() + { + ExcelFormats.DataPaddingFactor.ShouldBe(1.30); + } +} diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/MinimalSearchTests.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/MinimalSearchTests.cs new file mode 100644 index 0000000..e058160 --- /dev/null +++ b/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/MinimalSearchTests.cs @@ -0,0 +1,48 @@ +using ClosedXML.Excel; +using JdeScoping.ExcelIO.Tests.Fixtures; +using Shouldly; +using Xunit; + +namespace JdeScoping.ExcelIO.Tests.Integration; + +public class MinimalSearchTests : IClassFixture +{ + private readonly XLWorkbook _workbook; + + public MinimalSearchTests(MinimalSearchFixture fixture) + { + _workbook = fixture.Workbook; + } + + [Fact] + public void SheetCount_IsTwo() + { + _workbook.Worksheets.Count.ShouldBe(2); + } + + [Fact] + public void SearchCriteriaSheet_Exists() + { + _workbook.Worksheets.TryGetWorksheet("Search Criteria", out _).ShouldBeTrue(); + } + + [Fact] + public void SearchResultsSheet_Exists() + { + _workbook.Worksheets.TryGetWorksheet("Search Results", out _).ShouldBeTrue(); + } + + [Fact] + public void SearchCriteriaSheet_IsProtected() + { + var sheet = _workbook.Worksheet("Search Criteria"); + sheet.Protection.IsProtected.ShouldBeTrue(); + } + + [Fact] + public void SearchResultsSheet_IsProtected() + { + var sheet = _workbook.Worksheet("Search Results"); + sheet.Protection.IsProtected.ShouldBeTrue(); + } +} diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/MisInfoSheetTests.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/MisInfoSheetTests.cs new file mode 100644 index 0000000..dd9db0d --- /dev/null +++ b/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/MisInfoSheetTests.cs @@ -0,0 +1,130 @@ +using ClosedXML.Excel; +using JdeScoping.ExcelIO.Tests.Fixtures; +using Shouldly; +using Xunit; + +namespace JdeScoping.ExcelIO.Tests.Integration; + +public class MisInfoSheetTests : IClassFixture +{ + private readonly XLWorkbook _workbook; + private readonly IXLWorksheet _sheet; + private readonly List _headers; + + public MisInfoSheetTests(WithMisDataFixture fixture) + { + _workbook = fixture.Workbook; + _sheet = _workbook.Worksheet("MIS Info"); + _headers = ExcelTestHelpers.GetHeadersFromSheet(_sheet); + } + + [Fact] + public void SheetCount_IsFour() + { + _workbook.Worksheets.Count.ShouldBe(4); + } + + [Fact] + public void MisInfoSheet_Exists() + { + _workbook.Worksheets.TryGetWorksheet("MIS Info", out _).ShouldBeTrue(); + } + + [Fact] + public void ColumnCount_Is19() + { + _headers.Count.ShouldBe(19); + } + + [Fact] + public void ColumnHeaders_MatchExpected() + { + _headers.ShouldContain("Item Number"); + _headers.ShouldContain("MIS Job Step Sequence Number"); + _headers.ShouldContain("MIS Number"); + _headers.ShouldContain("MIS Revision"); + _headers.ShouldContain("Item Description"); + _headers.ShouldContain("MIS Release Status"); + _headers.ShouldContain("MIS Release Date"); + _headers.ShouldContain("Branch Code"); + _headers.ShouldContain("Job Step Sequence Number"); + _headers.ShouldContain("Matched Sequence Number"); + _headers.ShouldContain("Matched to F3112Z1?"); + _headers.ShouldContain("Matched to F3003?"); + _headers.ShouldContain("Function Operation Description"); + _headers.ShouldContain("Char Number"); + _headers.ShouldContain("Test Description"); + _headers.ShouldContain("Sampling Type"); + _headers.ShouldContain("Sampling Value"); + _headers.ShouldContain("Tools & Gauges"); + _headers.ShouldContain("Work Instructions"); + } + + [Fact] + public void ColumnOrder_MatchesSpec() + { + _headers[0].ShouldBe("Item Number"); + _headers[1].ShouldBe("MIS Job Step Sequence Number"); + _headers[2].ShouldBe("MIS Number"); + _headers[3].ShouldBe("MIS Revision"); + _headers[4].ShouldBe("Item Description"); + _headers[5].ShouldBe("MIS Release Status"); + _headers[6].ShouldBe("MIS Release Date"); + _headers[7].ShouldBe("Branch Code"); + _headers[8].ShouldBe("Job Step Sequence Number"); + _headers[9].ShouldBe("Matched Sequence Number"); + _headers[10].ShouldBe("Matched to F3112Z1?"); + _headers[11].ShouldBe("Matched to F3003?"); + _headers[12].ShouldBe("Function Operation Description"); + _headers[13].ShouldBe("Char Number"); + _headers[14].ShouldBe("Test Description"); + _headers[15].ShouldBe("Sampling Type"); + _headers[16].ShouldBe("Sampling Value"); + _headers[17].ShouldBe("Tools & Gauges"); + _headers[18].ShouldBe("Work Instructions"); + } + + [Fact] + public void TableStyle_IsLight18() + { + var table = _sheet.Tables.First(); + table.Theme.ShouldBe(XLTableTheme.TableStyleLight18); + } + + [Fact] + public void Protection_IsEnabled() + { + _sheet.Protection.IsProtected.ShouldBeTrue(); + } + + [Fact] + public void DataRow_ContainsExpectedValues() + { + _sheet.Cell(2, 1).Value.GetText().ShouldBe("ITEM-001"); + _sheet.Cell(2, 3).Value.GetText().ShouldBe("MIS-001"); + } + + [Fact] + public void TestDescriptionColumn_IsWrapped() + { + var colIndex = _headers.IndexOf("Test Description") + 1; + _sheet.Column(colIndex).Width.ShouldBe(65); + _sheet.Column(colIndex).Style.Alignment.WrapText.ShouldBeTrue(); + } + + [Fact] + public void ToolsGaugesColumn_IsWrapped() + { + var colIndex = _headers.IndexOf("Tools & Gauges") + 1; + _sheet.Column(colIndex).Width.ShouldBe(65); + _sheet.Column(colIndex).Style.Alignment.WrapText.ShouldBeTrue(); + } + + [Fact] + public void WorkInstructionsColumn_IsWrapped() + { + var colIndex = _headers.IndexOf("Work Instructions") + 1; + _sheet.Column(colIndex).Width.ShouldBe(65); + _sheet.Column(colIndex).Style.Alignment.WrapText.ShouldBeTrue(); + } +} diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/ProtectionAndStyleTests.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/ProtectionAndStyleTests.cs new file mode 100644 index 0000000..90e1384 --- /dev/null +++ b/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/ProtectionAndStyleTests.cs @@ -0,0 +1,75 @@ +using ClosedXML.Excel; +using JdeScoping.ExcelIO.Tests.Fixtures; +using Shouldly; +using Xunit; + +namespace JdeScoping.ExcelIO.Tests.Integration; + +public class ProtectionAndStyleTests : IClassFixture +{ + private readonly XLWorkbook _workbook; + + public ProtectionAndStyleTests(WithMisDataFixture fixture) + { + _workbook = fixture.Workbook; + } + + [Fact] + public void AllDataSheets_AreProtected() + { + _workbook.Worksheet("Search Results").Protection.IsProtected.ShouldBeTrue(); + _workbook.Worksheet("MIS Info").Protection.IsProtected.ShouldBeTrue(); + _workbook.Worksheet("Investigation").Protection.IsProtected.ShouldBeTrue(); + } + + [Fact] + public void Protection_AllowsFiltering() + { + var sheet = _workbook.Worksheet("Search Results"); + sheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.AutoFilter).ShouldBeTrue(); + } + + [Fact] + public void Protection_AllowsSorting() + { + var sheet = _workbook.Worksheet("Search Results"); + sheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.Sort).ShouldBeTrue(); + } + + [Fact] + public void Protection_AllowsFormatting() + { + var sheet = _workbook.Worksheet("Search Results"); + sheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.FormatCells).ShouldBeTrue(); + sheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.FormatColumns).ShouldBeTrue(); + sheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.FormatRows).ShouldBeTrue(); + } + + [Fact] + public void AllTables_UseLight18Style() + { + _workbook.Worksheet("Search Results").Tables.First().Theme.ShouldBe(XLTableTheme.TableStyleLight18); + _workbook.Worksheet("MIS Info").Tables.First().Theme.ShouldBe(XLTableTheme.TableStyleLight18); + _workbook.Worksheet("Investigation").Tables.First().Theme.ShouldBe(XLTableTheme.TableStyleLight18); + } + + [Fact] + public void HeaderCells_HaveCorrectFormatting() + { + var sheet = _workbook.Worksheet("Search Criteria"); + var headerCell = sheet.Cell(1, 1); + headerCell.Style.Font.Bold.ShouldBeTrue(); + headerCell.Style.Fill.BackgroundColor.ShouldBe(XLColor.Gainsboro); + headerCell.Style.Alignment.Horizontal.ShouldBe(XLAlignmentHorizontalValues.Center); + } + + [Fact] + public void CriteriaTimestamp_MatchesLegacyFormat() + { + var sheet = _workbook.Worksheet("Search Criteria"); + var timestamp = sheet.Cell(4, 2).Value.GetText(); + timestamp.ShouldContain("Jan 15, 2024"); + timestamp.ShouldContain("02:30:45"); + timestamp.ShouldContain("EST"); + } +} diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/SearchResultsSheetTests.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/SearchResultsSheetTests.cs new file mode 100644 index 0000000..40f4435 --- /dev/null +++ b/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/SearchResultsSheetTests.cs @@ -0,0 +1,89 @@ +using ClosedXML.Excel; +using JdeScoping.ExcelIO.Tests.Fixtures; +using Shouldly; +using Xunit; + +namespace JdeScoping.ExcelIO.Tests.Integration; + +public class SearchResultsSheetTests : IClassFixture +{ + private readonly XLWorkbook _workbook; + private readonly IXLWorksheet _sheet; + private readonly List _headers; + + public SearchResultsSheetTests(WithResultsFixture fixture) + { + _workbook = fixture.Workbook; + _sheet = _workbook.Worksheet("Search Results"); + _headers = ExcelTestHelpers.GetHeadersFromSheet(_sheet); + } + + [Fact] + public void ColumnCount_Is19() + { + _headers.Count.ShouldBe(19); + } + + [Fact] + public void ColumnHeaders_MatchExpected() + { + _headers.ShouldContain("Work Order Number"); + _headers.ShouldContain("Work Order Branch Code"); + _headers.ShouldContain("Lot Number"); + _headers.ShouldContain("Item Number"); + _headers.ShouldContain("Planning Family"); + _headers.ShouldContain("Stocking Type"); + _headers.ShouldContain("Order Quantity"); + _headers.ShouldContain("Held Quantity"); + _headers.ShouldContain("Scrapped Quantity"); + _headers.ShouldContain("Shipped Quantity"); + _headers.ShouldContain("Operation Step Branch Code"); + _headers.ShouldContain("Operation Step"); + _headers.ShouldContain("Operation Step Description"); + _headers.ShouldContain("Function Operation Description"); + _headers.ShouldContain("Operation Step Update Timestamp"); + _headers.ShouldContain("Status Code"); + _headers.ShouldContain("Status Description"); + _headers.ShouldContain("Status Update Timestamp"); + _headers.ShouldContain("Inclusion Reason"); + } + + [Fact] + public void ColumnOrder_MatchesSpec() + { + _headers[0].ShouldBe("Work Order Number"); + _headers[1].ShouldBe("Work Order Branch Code"); + _headers[2].ShouldBe("Lot Number"); + _headers[3].ShouldBe("Item Number"); + _headers[4].ShouldBe("Planning Family"); + _headers[5].ShouldBe("Stocking Type"); + _headers[6].ShouldBe("Order Quantity"); + _headers[7].ShouldBe("Held Quantity"); + _headers[8].ShouldBe("Scrapped Quantity"); + _headers[9].ShouldBe("Shipped Quantity"); + _headers[10].ShouldBe("Operation Step Branch Code"); + _headers[11].ShouldBe("Operation Step"); + _headers[12].ShouldBe("Operation Step Description"); + _headers[13].ShouldBe("Function Operation Description"); + _headers[14].ShouldBe("Operation Step Update Timestamp"); + _headers[15].ShouldBe("Status Code"); + _headers[16].ShouldBe("Status Description"); + _headers[17].ShouldBe("Status Update Timestamp"); + _headers[18].ShouldBe("Inclusion Reason"); + } + + [Fact] + public void TableStyle_IsLight18() + { + var table = _sheet.Tables.First(); + table.Theme.ShouldBe(XLTableTheme.TableStyleLight18); + } + + [Fact] + public void DataRow_ContainsExpectedValues() + { + _sheet.Cell(2, 1).Value.GetNumber().ShouldBe(12345); + _sheet.Cell(2, 3).Value.GetText().ShouldBe("LOT-001"); + _sheet.Cell(2, 4).Value.GetText().ShouldBe("ITEM-001"); + } +} diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/LegacyComparisonTests.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/LegacyComparisonTests.cs deleted file mode 100644 index 17b9619..0000000 --- a/NEW/tests/JdeScoping.ExcelIO.Tests/LegacyComparisonTests.cs +++ /dev/null @@ -1,527 +0,0 @@ -using ClosedXML.Excel; -using JdeScoping.ExcelIO.Options; -using JdeScoping.ExcelIO.Generators; -using JdeScoping.ExcelIO.Mapping; -using JdeScoping.ExcelIO.Mapping.Maps; -using JdeScoping.ExcelIO.Models.Reporting; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using NSubstitute; -using Shouldly; -using Xunit; - -using ExcelFormats = JdeScoping.ExcelIO.Formatting.ExcelFormats; -using SearchResult = JdeScoping.Core.Models.SearchResults.SearchResult; -using MisSearchResult = JdeScoping.Core.Models.SearchResults.MisSearchResult; -using MisNonMatchSearchResult = JdeScoping.Core.Models.SearchResults.MisNonMatchSearchResult; - -namespace JdeScoping.ExcelIO.Tests; - -/// -/// Tests comparing generated output against legacy format specifications. -/// These tests verify column order, format strings, and protection settings -/// match the legacy ExcelWriter.cs implementation. -/// -public class LegacyComparisonTests -{ - private readonly ExcelExportService _service; - - public LegacyComparisonTests() - { - var logger = Substitute.For>(); - var options = Microsoft.Extensions.Options.Options.Create(new ExcelExportOptions - { - CriteriaSheetPassword = "JDE_SCOPING_TOOL_PASS", - DataSheetPassword = "JDESCOPINGTOOL" - }); - - var registry = CreateTestRegistry(); - var tableWriter = new FluentTableWriter(registry); - var criteriaGenerator = new CriteriaSheetGenerator(options, tableWriter); - - _service = new ExcelExportService(logger, options, criteriaGenerator, tableWriter, registry); - } - - private static ExcelMapRegistry CreateTestRegistry() - { - var registry = new ExcelMapRegistry(); - - // Search result maps - registry.Register(new SearchResultMap()); - registry.Register(new MisSearchResultMap()); - registry.Register(new MisNonMatchSearchResultMap()); - - // Filter entry maps - registry.Register(new TimespanFilterMap()); - registry.Register(new WorkOrderFilterEntryMap()); - registry.Register(new ItemNumberFilterEntryMap()); - registry.Register(new ProfitCenterFilterEntryMap()); - registry.Register(new WorkCenterFilterEntryMap()); - registry.Register(new OperatorFilterEntryMap()); - registry.Register(new ComponentLotFilterEntryMap()); - registry.Register(new ItemOperationMisFilterEntryMap()); - - return registry; - } - - #region Search Results Column Order Tests - - /// - /// Verifies Search Results columns match legacy order per ExcelWriter.cs lines 197-218. - /// Legacy order: Work Order Number, Work Order Branch Code, Lot Number, Item Number, - /// Planning Family, Order Quantity, Held Quantity, Scrapped Quantity, Shipped Quantity, - /// Operation Step Branch Code, Operation Step, Operation Step Description, - /// Function Operation Description, Operation Step Update Timestamp, Status Code, - /// Status Description, Status Update Timestamp, Inclusion Reason - /// - /// Note: The new implementation adds "Stocking Type" after "Planning Family" per spec. - /// - [Fact] - public async Task SearchResults_ColumnOrder_MatchesLegacyWithEnhancements() - { - var search = CreateSearchModelWithResults(); - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - var sheet = workbook.Worksheet("Search Results"); - - // Verify key column positions (0-indexed from GetHeadersFromSheet) - var headers = GetHeadersFromSheet(sheet); - - headers[0].ShouldBe("Work Order Number"); - headers[1].ShouldBe("Work Order Branch Code"); - headers[2].ShouldBe("Lot Number"); - headers[3].ShouldBe("Item Number"); - headers[4].ShouldBe("Planning Family"); - // New column: Stocking Type (not in legacy) - headers[5].ShouldBe("Stocking Type"); - headers[6].ShouldBe("Order Quantity"); - headers[7].ShouldBe("Held Quantity"); - headers[8].ShouldBe("Scrapped Quantity"); - headers[9].ShouldBe("Shipped Quantity"); - headers[10].ShouldBe("Operation Step Branch Code"); - headers[11].ShouldBe("Operation Step"); - headers[12].ShouldBe("Operation Step Description"); - headers[13].ShouldBe("Function Operation Description"); - headers[14].ShouldBe("Operation Step Update Timestamp"); - headers[15].ShouldBe("Status Code"); - headers[16].ShouldBe("Status Description"); - headers[17].ShouldBe("Status Update Timestamp"); - headers[18].ShouldBe("Inclusion Reason"); - } - - #endregion - - #region MIS Info Column Order Tests - - /// - /// Verifies MIS Info columns match expected order per spec. - /// Legacy order per ExcelWriter.cs lines 299-330: - /// Item Number, Item Description, MIS Job Step Sequence Number, MIS Number, MIS Revision, - /// MIS Release Status, MIS Release Date, Branch Code, Job Step Sequence Number, - /// Matched Sequence Number, Matched to F3112Z1?, Matched to F3003?, - /// Function Operation Description, Char Number, Test Description, - /// Sampling Type, Sampling Value, Tools & Gauges, Work Instructions - /// - /// New implementation reorders to match attribute Order values. - /// - [Fact] - public async Task MisInfo_ColumnOrder_MatchesSpec() - { - var search = CreateSearchModelWithMisData(); - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - var sheet = workbook.Worksheet("MIS Info"); - - var headers = GetHeadersFromSheet(sheet); - - // Verify column order matches OutputColumn Order attributes - headers[0].ShouldBe("Item Number"); - headers[1].ShouldBe("MIS Job Step Sequence Number"); - headers[2].ShouldBe("MIS Number"); - headers[3].ShouldBe("MIS Revision"); - headers[4].ShouldBe("Item Description"); - headers[5].ShouldBe("MIS Release Status"); - headers[6].ShouldBe("MIS Release Date"); - headers[7].ShouldBe("Branch Code"); - headers[8].ShouldBe("Job Step Sequence Number"); - headers[9].ShouldBe("Matched Sequence Number"); - headers[10].ShouldBe("Matched to F3112Z1?"); - headers[11].ShouldBe("Matched to F3003?"); - headers[12].ShouldBe("Function Operation Description"); - headers[13].ShouldBe("Char Number"); - headers[14].ShouldBe("Test Description"); - headers[15].ShouldBe("Sampling Type"); - headers[16].ShouldBe("Sampling Value"); - headers[17].ShouldBe("Tools & Gauges"); - headers[18].ShouldBe("Work Instructions"); - } - - #endregion - - #region Investigation Column Order Tests - - /// - /// Verifies Investigation columns match expected order per spec. - /// Legacy order per ExcelWriter.cs lines 403-418: - /// Work Center Code, Work Order Number, Work Order Start Date, Job Step Number, - /// Function Operation Description, Job Step End Date, Function Code, - /// Item Number, Item Description, Routing Type - /// - /// New implementation adds: Was Job Step Added?, Matched Job Step Number - /// - [Fact] - public async Task Investigation_ColumnOrder_MatchesSpecWithEnhancements() - { - var search = CreateSearchModelWithMisData(); - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - var sheet = workbook.Worksheet("Investigation"); - - var headers = GetHeadersFromSheet(sheet); - - headers[0].ShouldBe("Work Center Code"); - headers[1].ShouldBe("Work Order Number"); - headers[2].ShouldBe("Work Order Start Date"); - headers[3].ShouldBe("Job Step Number"); - headers[4].ShouldBe("Function Operation Description"); - headers[5].ShouldBe("Job Step End Date"); - headers[6].ShouldBe("Function Code"); - // New columns per spec - headers[7].ShouldBe("Was Job Step Added?"); - headers[8].ShouldBe("Matched Job Step Number"); - headers[9].ShouldBe("Item Number"); - headers[10].ShouldBe("Item Description"); - headers[11].ShouldBe("Routing Type"); - } - - #endregion - - #region Format String Tests - - /// - /// Verifies timestamp format matches legacy TIMESTAMP_FORMAT = "[$-409]m/d/yy h:mm AM/PM;@" - /// per ExcelWriter.cs line 26. - /// - [Fact] - public void TimestampFormat_MatchesLegacy() - { - ExcelFormats.TimestampFormat.ShouldBe("[$-409]m/d/yy h:mm AM/PM;@"); - } - - /// - /// Verifies date format uses locale-aware format. - /// Legacy used "m/d/yyyy" for Investigation sheet dates. - /// - [Fact] - public void DateFormat_MatchesLegacyPattern() - { - // Legacy used "m/d/yyyy", new implementation uses "[$-409]MM/dd/yyyy;@" - // Both produce similar output, the new format includes locale specifier - ExcelFormats.DateFormat.ShouldContain("MM/dd/yyyy"); - } - - /// - /// Verifies wrapped column width matches legacy WRAPPED_CELL_WIDTH = 65 - /// per ExcelWriter.cs line 31. - /// - [Fact] - public void WrappedColumnWidth_MatchesLegacy() - { - ExcelFormats.WrappedColumnWidth.ShouldBe(65); - } - - /// - /// Verifies criteria sheet padding factor matches legacy 1.15 (15%) - /// per ExcelWriter.cs line 175. - /// - [Fact] - public void CriteriaPaddingFactor_MatchesLegacy() - { - ExcelFormats.CriteriaPaddingFactor.ShouldBe(1.15); - } - - /// - /// Verifies data sheet padding factor matches legacy 1.3 (30%) - /// per ExcelWriter.cs lines 251, 367, 442. - /// - [Fact] - public void DataPaddingFactor_MatchesLegacy() - { - ExcelFormats.DataPaddingFactor.ShouldBe(1.30); - } - - #endregion - - #region Protection Settings Tests - - /// - /// Verifies criteria sheet uses correct password per ExcelWriter.cs line 179. - /// Legacy password: "JDE_SCOPING_TOOL_PASS" - /// - [Fact] - public async Task CriteriaSheet_Protection_IsEnabled() - { - var search = CreateSearchModelWithResults(); - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - var sheet = workbook.Worksheet("Search Criteria"); - - sheet.Protection.IsProtected.ShouldBeTrue(); - } - - /// - /// Verifies data sheets use correct password per ExcelWriter.cs line 277, 496. - /// Legacy password: "JDESCOPINGTOOL" - /// - [Fact] - public async Task DataSheets_Protection_IsEnabled() - { - var search = CreateSearchModelWithMisData(); - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - - workbook.Worksheet("Search Results").Protection.IsProtected.ShouldBeTrue(); - workbook.Worksheet("MIS Info").Protection.IsProtected.ShouldBeTrue(); - workbook.Worksheet("Investigation").Protection.IsProtected.ShouldBeTrue(); - } - - /// - /// Verifies protection allows filtering per legacy settings. - /// Legacy: AllowAutoFilter = true (line 268) - /// - [Fact] - public async Task DataSheets_Protection_AllowsFiltering() - { - var search = CreateSearchModelWithMisData(); - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - - var resultsSheet = workbook.Worksheet("Search Results"); - resultsSheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.AutoFilter).ShouldBeTrue(); - } - - /// - /// Verifies protection allows sorting per legacy settings. - /// Legacy: AllowSort = true (line 276) - /// - [Fact] - public async Task DataSheets_Protection_AllowsSorting() - { - var search = CreateSearchModelWithMisData(); - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - - var resultsSheet = workbook.Worksheet("Search Results"); - resultsSheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.Sort).ShouldBeTrue(); - } - - /// - /// Verifies protection allows formatting per legacy settings. - /// Legacy: AllowFormatCells, AllowFormatColumns, AllowFormatRows = true (lines 270-272) - /// - [Fact] - public async Task DataSheets_Protection_AllowsFormatting() - { - var search = CreateSearchModelWithMisData(); - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - - var resultsSheet = workbook.Worksheet("Search Results"); - resultsSheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.FormatCells).ShouldBeTrue(); - resultsSheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.FormatColumns).ShouldBeTrue(); - resultsSheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.FormatRows).ShouldBeTrue(); - } - - #endregion - - #region Table Style Tests - - /// - /// Note: Legacy used TableStyles.Medium1 per ExcelWriter.cs line 261. - /// New implementation uses Light18 per spec requirement. - /// This is an intentional change documented in the spec. - /// - [Fact] - public async Task DataSheets_UseLight18TableStyle_PerSpec() - { - var search = CreateSearchModelWithMisData(); - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - - var resultsSheet = workbook.Worksheet("Search Results"); - var table = resultsSheet.Tables.First(); - // Spec specifies Light18, legacy used Medium1 - table.Theme.ShouldBe(XLTableTheme.TableStyleLight18); - } - - #endregion - - #region Timestamp Formatting Tests - - /// - /// Verifies criteria sheet timestamp format matches legacy. - /// Legacy format per line 98: "{searchModel.SubmitDT:MMM dd, yyyy hh:mm:ss tt} EST" - /// - [Fact] - public async Task CriteriaSheet_TimestampFormat_MatchesLegacy() - { - var search = CreateSearchModelWithResults(); - search.SubmitDt = new DateTime(2024, 1, 15, 14, 30, 45); - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - var sheet = workbook.Worksheet("Search Criteria"); - - var submitTimestamp = sheet.Cell(4, 2).Value.GetText(); - submitTimestamp.ShouldContain("Jan 15, 2024"); - submitTimestamp.ShouldContain("02:30:45"); - submitTimestamp.ShouldContain("EST"); - } - - #endregion - - #region Header Formatting Tests - - /// - /// Verifies header cell formatting matches legacy ApplyHeaderFormat. - /// Legacy per lines 467-476: Bold, centered, Gainsboro background. - /// - [Fact] - public async Task Headers_Formatting_MatchesLegacy() - { - var search = CreateSearchModelWithResults(); - var result = await _service.GenerateAsync(search); - - using var stream = new MemoryStream(result); - using var workbook = new XLWorkbook(stream); - var sheet = workbook.Worksheet("Search Criteria"); - - // Check "Search Name" header cell - var headerCell = sheet.Cell(1, 1); - headerCell.Style.Font.Bold.ShouldBeTrue(); - headerCell.Style.Fill.BackgroundColor.ShouldBe(XLColor.Gainsboro); - headerCell.Style.Alignment.Horizontal.ShouldBe(XLAlignmentHorizontalValues.Center); - } - - #endregion - - #region Helper Methods - - private static List GetHeadersFromSheet(IXLWorksheet sheet) - { - var headers = new List(); - var col = 1; - while (!sheet.Cell(1, col).IsEmpty()) - { - headers.Add(sheet.Cell(1, col).Value.GetText()); - col++; - } - return headers; - } - - private static SearchModel CreateSearchModelWithResults() - { - return new SearchModel - { - Id = 1, - Name = "Legacy Comparison Test", - UserName = "testuser", - SubmitDt = DateTime.Now.AddHours(-1), - StartDt = DateTime.Now.AddMinutes(-30), - EndDt = DateTime.Now, - ExtractMisData = false, - Results = [ - new SearchResult - { - WorkOrderNumber = 12345, - WorkOrderBranchCode = "001", - LotNumber = "LOT-001", - ItemNumber = "ITEM-001", - PlanningFamily = "PF01", - StockingType = "M", - OrderQuantity = 100, - HeldQuantity = 0, - ScrappedQuantity = 0, - ShippedQuantity = 50, - StepBranchCode = "001", - StepNumber = 10, - StepDescription = "Assembly", - FunctionOperationDescription = "Main assembly", - StepUpdateDt = DateTime.Now, - StatusCode = "50", - StatusDescription = "In Progress", - Flagged = true - } - ] - }; - } - - private static SearchModel CreateSearchModelWithMisData() - { - var model = CreateSearchModelWithResults(); - model.ExtractMisData = true; - model.MisResults = [ - new MisSearchResult - { - ItemNumber = "ITEM-001", - SequenceNumber = "010", - MisNumber = "MIS-001", - RevId = "A", - ItemDescription = "Test Item", - Status = "Released", - ReleaseDate = DateTime.Now.AddDays(-30), - BranchCode = "001", - JobStepSequenceNumber = 10, - MatchedSequenceNumber = 10, - RoutingMatch = true, - MasterMatch = true, - FunctionOperationDescription = "Assembly operation", - CharNumber = "001", - TestDescription = "Sample test description", - SamplingType = "100%", - SamplingValue = "1", - ToolsGauges = "Gauge A, Gauge B", - WorkInstructions = "Step 1: Do this. Step 2: Do that." - } - ]; - model.MisNonMatchResults = [ - new MisNonMatchSearchResult - { - WorkCenterCode = "WC01", - WorkOrderNumber = 12345, - WorkOrderStartDate = DateTime.Now.AddDays(-7), - JobStepNumber = 10, - JobStepDescription = "Test operation", - JobStepEndDate = DateTime.Now.AddDays(-5), - FunctionCode = "FC01", - WasJobStepAdded = false, - MatchedJobStepNumber = 10, - ItemNumber = "ITEM-001", - ItemDescription = "Test Item Description", - RoutingType = "M" - } - ]; - return model; - } - - #endregion -}