diff --git a/NEW/docs/plans/2026-01-07-excelio-test-optimization.md b/NEW/docs/plans/2026-01-07-excelio-test-optimization.md deleted file mode 100644 index 520e898..0000000 --- a/NEW/docs/plans/2026-01-07-excelio-test-optimization.md +++ /dev/null @@ -1,1030 +0,0 @@ -# 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/docs/plans/2026-01-21-remove-pipeline-viewer.md b/NEW/docs/plans/2026-01-21-remove-pipeline-viewer.md deleted file mode 100644 index 27383e9..0000000 --- a/NEW/docs/plans/2026-01-21-remove-pipeline-viewer.md +++ /dev/null @@ -1,329 +0,0 @@ -# Remove Pipeline Viewer Implementation Plan - -> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. - -**Goal:** Remove the pipeline viewer feature from the web UI and clean up all supporting code (controllers, DTOs, client services, routes). - -**Architecture:** Delete files in dependency order (DTOs/interfaces first would break compilation), so we delete leaf components first (views, client), then API layer, then shared contracts/DTOs. Modify DI registrations and navigation. - -**Tech Stack:** Blazor WebAssembly, ASP.NET Core API, C# - ---- - -## Task 1: Remove Client Components (Blazor Pages) - -**Files:** -- Delete: `src/JdeScoping.Client/Pages/Admin/PipelineViewer.razor` -- Delete: `src/JdeScoping.Client/Components/Admin/PipelineScheduleSection.razor` -- Delete: `src/JdeScoping.Client/Components/Admin/SqlQueryModal.razor` - -**Step 1: Delete PipelineViewer.razor** - -```bash -rm src/JdeScoping.Client/Pages/Admin/PipelineViewer.razor -``` - -**Step 2: Delete PipelineScheduleSection.razor** - -```bash -rm src/JdeScoping.Client/Components/Admin/PipelineScheduleSection.razor -``` - -**Step 3: Delete SqlQueryModal.razor (if exists)** - -```bash -rm -f src/JdeScoping.Client/Components/Admin/SqlQueryModal.razor -``` - -**Step 4: Verify files deleted** - -```bash -ls src/JdeScoping.Client/Pages/Admin/ | grep -i pipeline || echo "No pipeline pages remain" -ls src/JdeScoping.Client/Components/Admin/ | grep -i pipeline || echo "No pipeline components remain" -``` - -Expected: No pipeline-related files in Admin folders. - ---- - -## Task 2: Remove Navigation Link - -**Files:** -- Modify: `src/JdeScoping.Client/Layout/MainLayout.razor:16` - -**Step 1: Remove the pipeline viewer nav link** - -In `MainLayout.razor`, delete this line (around line 16): -```razor -Pipeline Viewer -``` - -**Step 2: Verify the change** - -```bash -grep -n "pipeline-viewer" src/JdeScoping.Client/Layout/MainLayout.razor || echo "Nav link removed" -``` - -Expected: "Nav link removed" - ---- - -## Task 3: Remove Client Service - -**Files:** -- Delete: `src/JdeScoping.Client/Services/PipelineApiClient.cs` -- Modify: `src/JdeScoping.Client/Program.cs:58` - -**Step 1: Delete PipelineApiClient.cs** - -```bash -rm src/JdeScoping.Client/Services/PipelineApiClient.cs -``` - -**Step 2: Remove DI registration from Program.cs** - -In `src/JdeScoping.Client/Program.cs`, delete this line (around line 58): -```csharp -builder.Services.AddScoped(); -``` - -**Step 3: Verify the changes** - -```bash -grep -n "PipelineApiClient" src/JdeScoping.Client/Program.cs || echo "DI registration removed" -ls src/JdeScoping.Client/Services/PipelineApiClient.cs 2>/dev/null || echo "File deleted" -``` - -Expected: Both checks show removal confirmed. - ---- - -## Task 4: Remove API Controller and Mapper - -**Files:** -- Delete: `src/JdeScoping.Api/Controllers/PipelineController.cs` -- Delete: `src/JdeScoping.Api/Mapping/PipelineMapper.cs` -- Delete: `src/JdeScoping.Api/Mapping/IPipelineMapper.cs` -- Modify: `src/JdeScoping.Api/DependencyInjection.cs:43` - -**Step 1: Delete PipelineController.cs** - -```bash -rm src/JdeScoping.Api/Controllers/PipelineController.cs -``` - -**Step 2: Delete PipelineMapper.cs** - -```bash -rm src/JdeScoping.Api/Mapping/PipelineMapper.cs -``` - -**Step 3: Delete IPipelineMapper.cs** - -```bash -rm src/JdeScoping.Api/Mapping/IPipelineMapper.cs -``` - -**Step 4: Remove DI registration from DependencyInjection.cs** - -In `src/JdeScoping.Api/DependencyInjection.cs`, delete this line (around line 43): -```csharp -services.AddSingleton(); -``` - -**Step 5: Verify the changes** - -```bash -ls src/JdeScoping.Api/Controllers/PipelineController.cs 2>/dev/null || echo "Controller deleted" -ls src/JdeScoping.Api/Mapping/*Pipeline* 2>/dev/null || echo "Mapper files deleted" -grep -n "PipelineMapper" src/JdeScoping.Api/DependencyInjection.cs || echo "DI registration removed" -``` - -Expected: All three checks confirm removal. - ---- - -## Task 5: Remove Core DTOs and Interface - -**Files:** -- Delete: `src/JdeScoping.Core/Models/Pipelines/` (entire folder) -- Delete: `src/JdeScoping.Core/ApiContracts/IPipelineApiClient.cs` - -**Step 1: Delete the Pipelines DTO folder** - -```bash -rm -rf src/JdeScoping.Core/Models/Pipelines/ -``` - -**Step 2: Delete IPipelineApiClient.cs** - -```bash -rm src/JdeScoping.Core/ApiContracts/IPipelineApiClient.cs -``` - -**Step 3: Verify deletions** - -```bash -ls src/JdeScoping.Core/Models/Pipelines/ 2>/dev/null || echo "Pipelines folder deleted" -ls src/JdeScoping.Core/ApiContracts/IPipelineApiClient.cs 2>/dev/null || echo "Interface deleted" -``` - -Expected: Both checks confirm deletion. - ---- - -## Task 6: Remove API Routes - -**Files:** -- Modify: `src/JdeScoping.Core/ApiContracts/ApiRoutes.cs:155-188` - -**Step 1: Remove the Pipelines class from ApiRoutes.cs** - -Delete the entire `Pipelines` static class (lines 155-188): -```csharp - /// - /// Routes for pipeline configuration API endpoints. - /// - public static class Pipelines - { - /// Base route for pipeline endpoints. - public const string Base = "api/pipelines"; - - /// Route template for getting a pipeline by name. - public const string ByName = "{name}"; - - /// Route template for getting pipeline status. - public const string Status = "{name}/status"; - - /// Route template for getting pipeline executions. - public const string Executions = "{name}/executions"; - - /// Builds the route to get a specific pipeline config. - /// The pipeline name to URL-encode. - /// The formatted route. - public static string GetByName(string name) => $"api/pipelines/{Uri.EscapeDataString(name)}"; - - /// Builds the route to get pipeline status. - /// The pipeline name to URL-encode. - /// The formatted route. - public static string GetStatus(string name) => $"api/pipelines/{Uri.EscapeDataString(name)}/status"; - - /// Builds the route to get pipeline executions. - /// The pipeline name to URL-encode. - /// The number of recent executions to retrieve. - /// The formatted route. - public static string GetExecutions(string name, int count = 10) => - $"api/pipelines/{Uri.EscapeDataString(name)}/executions?count={count}"; - } -``` - -**Step 2: Verify the change** - -```bash -grep -n "Pipelines" src/JdeScoping.Core/ApiContracts/ApiRoutes.cs || echo "Pipelines routes removed" -``` - -Expected: "Pipelines routes removed" - ---- - -## Task 7: Delete Plan Documents - -**Files:** -- Delete: `docs/plans/2026-01-07-pipeline-viewer-design.md` -- Delete: `docs/plans/2026-01-07-pipeline-viewer-implementation.md` - -**Step 1: Delete design document** - -```bash -rm docs/plans/2026-01-07-pipeline-viewer-design.md -``` - -**Step 2: Delete implementation plan** - -```bash -rm docs/plans/2026-01-07-pipeline-viewer-implementation.md -``` - -**Step 3: Verify deletions** - -```bash -ls docs/plans/*pipeline-viewer* 2>/dev/null || echo "Plan documents deleted" -``` - -Expected: "Plan documents deleted" - ---- - -## Task 8: Build and Verify - -**Step 1: Build the solution** - -```bash -cd /Users/dohertj2/Desktop/JdeScopingTool/NEW && dotnet build -``` - -Expected: Build succeeds with no errors. - -**Step 2: Search for any remaining pipeline viewer references** - -```bash -grep -r "PipelineViewer\|PipelineApiClient\|IPipelineApiClient\|PipelineController\|IPipelineMapper\|PipelineMapper\|pipeline-viewer" src/ --include="*.cs" --include="*.razor" || echo "No references remain" -``` - -Expected: "No references remain" - -**Step 3: Run tests** - -```bash -dotnet test -``` - -Expected: All tests pass. - ---- - -## Task 9: Commit - -**Step 1: Stage all changes** - -```bash -git add -A -``` - -**Step 2: Commit** - -```bash -git commit -m "$(cat <<'EOF' -refactor(webui): remove pipeline viewer feature - -Remove the read-only pipeline viewer from the web UI: -- Delete PipelineViewer.razor page and supporting components -- Delete PipelineController and PipelineMapper from API -- Delete Pipeline DTOs from Core -- Delete PipelineApiClient from Client -- Remove navigation link and DI registrations -- Delete obsolete plan documents - -The ConfigManager utility retains pipeline editing capabilities. -EOF -)" -``` - ---- - -## Summary - -| Task | Action | Files Affected | -|------|--------|----------------| -| 1 | Delete Blazor components | 3 files deleted | -| 2 | Remove nav link | 1 file modified | -| 3 | Remove client service | 1 deleted, 1 modified | -| 4 | Remove API layer | 3 deleted, 1 modified | -| 5 | Remove Core DTOs | 5 files deleted | -| 6 | Remove API routes | 1 file modified | -| 7 | Delete plan docs | 2 files deleted | -| 8 | Build and verify | - | -| 9 | Commit | - | - -**Total:** 14 files deleted, 4 files modified diff --git a/NEW/src/JdeScoping.Client/Auth/AuthStateProvider.cs b/NEW/src/JdeScoping.Client/Auth/AuthStateProvider.cs index 7b12864..323f8a5 100644 --- a/NEW/src/JdeScoping.Client/Auth/AuthStateProvider.cs +++ b/NEW/src/JdeScoping.Client/Auth/AuthStateProvider.cs @@ -1,5 +1,6 @@ using System.Net.Http.Json; using System.Security.Claims; +using JdeScoping.Core.ApiContracts; using JdeScoping.Core.Models.Auth; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.Extensions.Logging; @@ -63,7 +64,7 @@ public class AuthStateProvider : AuthenticationStateProvider, IAuthStateProvider { try { - var response = await _httpClient.GetAsync("api/auth/me"); + var response = await _httpClient.GetAsync(ApiRoutes.Auth.Me); if (response.IsSuccessStatusCode) { return await response.Content.ReadFromJsonAsync(); diff --git a/NEW/src/JdeScoping.Client/Components/DataSync/NewSyncRequestDialog.razor.cs b/NEW/src/JdeScoping.Client/Components/DataSync/NewSyncRequestDialog.razor.cs index de4cfd7..51a5698 100644 --- a/NEW/src/JdeScoping.Client/Components/DataSync/NewSyncRequestDialog.razor.cs +++ b/NEW/src/JdeScoping.Client/Components/DataSync/NewSyncRequestDialog.razor.cs @@ -1,4 +1,5 @@ using System.Net.Http.Json; +using JdeScoping.Core.ApiContracts; using JdeScoping.Core.ViewModels; using Microsoft.AspNetCore.Components; using Radzen; @@ -88,7 +89,7 @@ public partial class NewSyncRequestDialog : ComponentBase SyncType = _selectedSyncType! }; - var response = await HttpClient.PostAsJsonAsync("api/manual-sync", createDto); + var response = await HttpClient.PostAsJsonAsync(ApiRoutes.ManualSync.Base, createDto); if (response.IsSuccessStatusCode) { diff --git a/NEW/src/JdeScoping.Client/Services/AuthService.cs b/NEW/src/JdeScoping.Client/Services/AuthService.cs index 00c838a..3dc2777 100644 --- a/NEW/src/JdeScoping.Client/Services/AuthService.cs +++ b/NEW/src/JdeScoping.Client/Services/AuthService.cs @@ -1,5 +1,6 @@ using System.Net.Http.Json; using JdeScoping.Client.Auth; +using JdeScoping.Core.ApiContracts; using JdeScoping.Core.Models.Auth; namespace JdeScoping.Client.Services; @@ -43,7 +44,7 @@ public class AuthService : IAuthService var request = new EncryptedLoginRequest(encryptedData); // Send encrypted request - var response = await _httpClient.PostAsJsonAsync("api/auth/login", request); + var response = await _httpClient.PostAsJsonAsync(ApiRoutes.Auth.Login, request); var result = await response.Content.ReadFromJsonAsync(); if (result is null) @@ -72,7 +73,7 @@ public class AuthService : IAuthService { try { - await _httpClient.PostAsync("api/auth/logout", null); + await _httpClient.PostAsync(ApiRoutes.Auth.Logout, null); } catch { diff --git a/NEW/src/JdeScoping.Client/Services/CryptoService.cs b/NEW/src/JdeScoping.Client/Services/CryptoService.cs index f73f151..01b1ff6 100644 --- a/NEW/src/JdeScoping.Client/Services/CryptoService.cs +++ b/NEW/src/JdeScoping.Client/Services/CryptoService.cs @@ -1,5 +1,6 @@ using System.Net.Http.Json; using System.Text.Json; +using JdeScoping.Core.ApiContracts; using JdeScoping.Core.Models.Auth; using Microsoft.JSInterop; @@ -57,7 +58,7 @@ public class CryptoService : ICryptoService, IAsyncDisposable if (_cachedPublicKeyPem is not null) return _cachedPublicKeyPem; - var response = await _httpClient.GetFromJsonAsync("api/auth/public-key") + var response = await _httpClient.GetFromJsonAsync(ApiRoutes.Auth.PublicKey) ?? throw new InvalidOperationException("Failed to fetch public key from server"); _cachedPublicKeyPem = response.PublicKeyPem; diff --git a/NEW/src/JdeScoping.Client/Services/HubConnectionService.cs b/NEW/src/JdeScoping.Client/Services/HubConnectionService.cs index 1ce954d..46816bd 100644 --- a/NEW/src/JdeScoping.Client/Services/HubConnectionService.cs +++ b/NEW/src/JdeScoping.Client/Services/HubConnectionService.cs @@ -1,3 +1,4 @@ +using JdeScoping.Core.ApiContracts; using JdeScoping.Core.ViewModels; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.SignalR.Client; @@ -50,7 +51,7 @@ public class HubConnectionService : IHubConnectionService, IAsyncDisposable // In Blazor WebAssembly, the browser automatically sends cookies with requests // to the same origin, so we don't need to configure any special auth options _hubConnection = new HubConnectionBuilder() - .WithUrl(_navigationManager.ToAbsoluteUri("/hubs/status")) + .WithUrl(_navigationManager.ToAbsoluteUri(ApiRoutes.Hubs.Status)) .WithAutomaticReconnect([ TimeSpan.Zero, TimeSpan.FromSeconds(2), diff --git a/NEW/src/JdeScoping.Client/Services/RefreshStatusService.cs b/NEW/src/JdeScoping.Client/Services/RefreshStatusService.cs index 5c4b5e8..f13eebb 100644 --- a/NEW/src/JdeScoping.Client/Services/RefreshStatusService.cs +++ b/NEW/src/JdeScoping.Client/Services/RefreshStatusService.cs @@ -1,4 +1,5 @@ using System.Net.Http.Json; +using JdeScoping.Core.ApiContracts; using JdeScoping.Core.Models.Infrastructure; namespace JdeScoping.Client.Services; @@ -30,10 +31,8 @@ public class RefreshStatusService : IRefreshStatusService { try { - var minDtStr = minDt.ToString("yyyy-MM-dd"); - var maxDtStr = maxDt.ToString("yyyy-MM-dd"); var result = await _httpClient.GetFromJsonAsync>( - $"api/refresh-status?minDT={minDtStr}&maxDT={maxDtStr}"); + ApiRoutes.RefreshStatus.Get(minDt, maxDt)); return result ?? []; } catch (Exception ex) diff --git a/NEW/src/JdeScoping.Core/ApiContracts/ApiRoutes.cs b/NEW/src/JdeScoping.Core/ApiContracts/ApiRoutes.cs index 4371dd3..0d64c02 100644 --- a/NEW/src/JdeScoping.Core/ApiContracts/ApiRoutes.cs +++ b/NEW/src/JdeScoping.Core/ApiContracts/ApiRoutes.cs @@ -192,4 +192,13 @@ public static class ApiRoutes /// Route to reload pipelines. public const string Reload = "api/pipelines/reload"; } + + /// + /// Routes for SignalR hub endpoints. + /// + public static class Hubs + { + /// Route for the status hub (real-time search updates). + public const string Status = "/hubs/status"; + } }