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
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Integration tests that generate actual .xlsx files and verify structure with ClosedXML.
|
||||
/// </summary>
|
||||
public class ExcelExportIntegrationTests
|
||||
{
|
||||
private readonly ExcelExportService _service;
|
||||
private readonly ILogger<ExcelExportService> _logger;
|
||||
private readonly IOptions<ExcelExportOptions> _options;
|
||||
|
||||
public ExcelExportIntegrationTests()
|
||||
{
|
||||
_logger = Substitute.For<ILogger<ExcelExportService>>();
|
||||
_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<string> GetHeadersFromSheet(IXLWorksheet sheet)
|
||||
{
|
||||
var headers = new List<string>();
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using ClosedXML.Excel;
|
||||
|
||||
namespace JdeScoping.ExcelIO.Tests.Fixtures;
|
||||
|
||||
public static class ExcelTestHelpers
|
||||
{
|
||||
public static List<string> GetHeadersFromSheet(IXLWorksheet sheet)
|
||||
{
|
||||
var headers = new List<string>();
|
||||
var col = 1;
|
||||
while (!sheet.Cell(1, col).IsEmpty())
|
||||
{
|
||||
headers.Add(sheet.Cell(1, col).Value.GetText());
|
||||
col++;
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 = []
|
||||
};
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
@@ -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<ILogger<ExcelExportService>>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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<WithMisDataFixture>
|
||||
{
|
||||
private readonly XLWorkbook _workbook;
|
||||
private readonly IXLWorksheet _sheet;
|
||||
private readonly List<string> _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");
|
||||
}
|
||||
}
|
||||
@@ -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<LargeDataSetFixture>
|
||||
{
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ExcelFormats = JdeScoping.ExcelIO.Formatting.ExcelFormats;
|
||||
|
||||
namespace JdeScoping.ExcelIO.Tests.Integration;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for ExcelFormats constants - no workbook generation needed.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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<MinimalSearchFixture>
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -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<WithMisDataFixture>
|
||||
{
|
||||
private readonly XLWorkbook _workbook;
|
||||
private readonly IXLWorksheet _sheet;
|
||||
private readonly List<string> _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();
|
||||
}
|
||||
}
|
||||
@@ -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<WithMisDataFixture>
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -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<WithResultsFixture>
|
||||
{
|
||||
private readonly XLWorkbook _workbook;
|
||||
private readonly IXLWorksheet _sheet;
|
||||
private readonly List<string> _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");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Tests comparing generated output against legacy format specifications.
|
||||
/// These tests verify column order, format strings, and protection settings
|
||||
/// match the legacy ExcelWriter.cs implementation.
|
||||
/// </summary>
|
||||
public class LegacyComparisonTests
|
||||
{
|
||||
private readonly ExcelExportService _service;
|
||||
|
||||
public LegacyComparisonTests()
|
||||
{
|
||||
var logger = Substitute.For<ILogger<ExcelExportService>>();
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[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
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[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
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
[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
|
||||
|
||||
/// <summary>
|
||||
/// Verifies timestamp format matches legacy TIMESTAMP_FORMAT = "[$-409]m/d/yy h:mm AM/PM;@"
|
||||
/// per ExcelWriter.cs line 26.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void TimestampFormat_MatchesLegacy()
|
||||
{
|
||||
ExcelFormats.TimestampFormat.ShouldBe("[$-409]m/d/yy h:mm AM/PM;@");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies date format uses locale-aware format.
|
||||
/// Legacy used "m/d/yyyy" for Investigation sheet dates.
|
||||
/// </summary>
|
||||
[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");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies wrapped column width matches legacy WRAPPED_CELL_WIDTH = 65
|
||||
/// per ExcelWriter.cs line 31.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void WrappedColumnWidth_MatchesLegacy()
|
||||
{
|
||||
ExcelFormats.WrappedColumnWidth.ShouldBe(65);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies criteria sheet padding factor matches legacy 1.15 (15%)
|
||||
/// per ExcelWriter.cs line 175.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void CriteriaPaddingFactor_MatchesLegacy()
|
||||
{
|
||||
ExcelFormats.CriteriaPaddingFactor.ShouldBe(1.15);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies data sheet padding factor matches legacy 1.3 (30%)
|
||||
/// per ExcelWriter.cs lines 251, 367, 442.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void DataPaddingFactor_MatchesLegacy()
|
||||
{
|
||||
ExcelFormats.DataPaddingFactor.ShouldBe(1.30);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Protection Settings Tests
|
||||
|
||||
/// <summary>
|
||||
/// Verifies criteria sheet uses correct password per ExcelWriter.cs line 179.
|
||||
/// Legacy password: "JDE_SCOPING_TOOL_PASS"
|
||||
/// </summary>
|
||||
[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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies data sheets use correct password per ExcelWriter.cs line 277, 496.
|
||||
/// Legacy password: "JDESCOPINGTOOL"
|
||||
/// </summary>
|
||||
[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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies protection allows filtering per legacy settings.
|
||||
/// Legacy: AllowAutoFilter = true (line 268)
|
||||
/// </summary>
|
||||
[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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies protection allows sorting per legacy settings.
|
||||
/// Legacy: AllowSort = true (line 276)
|
||||
/// </summary>
|
||||
[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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies protection allows formatting per legacy settings.
|
||||
/// Legacy: AllowFormatCells, AllowFormatColumns, AllowFormatRows = true (lines 270-272)
|
||||
/// </summary>
|
||||
[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
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[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
|
||||
|
||||
/// <summary>
|
||||
/// Verifies criteria sheet timestamp format matches legacy.
|
||||
/// Legacy format per line 98: "{searchModel.SubmitDT:MMM dd, yyyy hh:mm:ss tt} EST"
|
||||
/// </summary>
|
||||
[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
|
||||
|
||||
/// <summary>
|
||||
/// Verifies header cell formatting matches legacy ApplyHeaderFormat.
|
||||
/// Legacy per lines 467-476: Bold, centered, Gainsboro background.
|
||||
/// </summary>
|
||||
[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<string> GetHeadersFromSheet(IXLWorksheet sheet)
|
||||
{
|
||||
var headers = new List<string>();
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user