Upgrade NPOI and automate validation

This commit is contained in:
Andy Howard
2026-09-12 17:00:28 +01:00
parent c510adc95c
commit 8cbef7f847
7 changed files with 129 additions and 5 deletions
+4
View File
@@ -16,6 +16,10 @@ dotnet build SaCommonVS.sln --configuration Debug
dotnet test SacsCoreUnitTests/SacsCoreUnitTests.csproj --configuration Debug dotnet test SacsCoreUnitTests/SacsCoreUnitTests.csproj --configuration Debug
``` ```
For the standard repository validation, including the focused spreadsheet tests, package audit, line-ending checks, and downstream BusinessMgr validation, run `./eng/validate.ps1`. See `eng/README.md` for its deliberately scoped options.
When validation requirements, solution structure, target frameworks, test projects, package-audit scope, required tooling, or supported downstream consumers change, update `eng/validate.ps1` and `eng/README.md` as part of the same change. Run the updated script before handoff. Do not bypass a failing validation stage without documenting the reason.
Use `--configuration Release` for release validation. Build the directly affected project first, then build the solution when a change affects public APIs or shared behavior. Mobile projects may require installed MAUI workloads and platform-specific tooling; report any unavailable workload separately from source failures. Use `--configuration Release` for release validation. Build the directly affected project first, then build the solution when a change affects public APIs or shared behavior. Mobile projects may require installed MAUI workloads and platform-specific tooling; report any unavailable workload separately from source failures.
For changes to Common public APIs, target frameworks, packages, or shared behavior, validate the relevant downstream solutions. Coordinate framework upgrades across dependency boundaries. When consumers cannot move atomically, temporarily multi-target shared libraries where practical, migrate consumers, and remove the old target only after no in-scope consumer requires it. For changes to Common public APIs, target frameworks, packages, or shared behavior, validate the relevant downstream solutions. Coordinate framework upgrades across dependency boundaries. When consumers cannot move atomically, temporarily multi-target shared libraries where practical, migrate consumers, and remove the old target only after no in-scope consumer requires it.
@@ -850,7 +850,7 @@ namespace Sarand.Net.Standard.Proc.Excel
{ {
// Ref. https://stackoverflow.com/questions/23280533/apache-poi-getting-a-null-pointer-while-reading-a-cell-with-value // Ref. https://stackoverflow.com/questions/23280533/apache-poi-getting-a-null-pointer-while-reading-a-cell-with-value
// recommends using DateTime.FromOADate(cell.NumericCellValue) rather than cell.DateCellValue // recommends using DateTime.FromOADate(cell.NumericCellValue) rather than cell.DateCellValue
return new DataValue(cell.DateCellValue); return new DataValue(DateTime.FromOADate(cell.NumericCellValue));
} }
if (IsCellNumeric(cell)) if (IsCellNumeric(cell))
{ {
@@ -1477,7 +1477,7 @@ namespace Sarand.Net.Standard.Proc.Excel
byte red = (byte)((rgbColour >> 0) & 0xff); byte red = (byte)((rgbColour >> 0) & 0xff);
byte green = (byte)((rgbColour >> 8) & 0xff); byte green = (byte)((rgbColour >> 8) & 0xff);
byte blue = (byte)((rgbColour >> 16) & 0xff); byte blue = (byte)((rgbColour >> 16) & 0xff);
IIndexedColorMap colorMap = stylesTable.GetIndexedColors(); IIndexedColorMap colorMap = stylesTable.IndexedColors;
for (int styleIdx = 0; styleIdx < stylesTable.NumCellStyles; styleIdx++) for (int styleIdx = 0; styleIdx < stylesTable.NumCellStyles; styleIdx++)
{ {
ICellStyle style = stylesTable.GetStyleAt(styleIdx); ICellStyle style = stylesTable.GetStyleAt(styleIdx);
+2 -1
View File
@@ -11,7 +11,8 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="DocumentFormat.OpenXml" Version="3.3.0" /> <PackageReference Include="DocumentFormat.OpenXml" Version="3.3.0" />
<PackageReference Include="NPOI" Version="2.6.2" /> <PackageReference Include="NPOI" Version="2.7.6" />
<PackageReference Include="System.Security.Cryptography.Xml" Version="10.0.12" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
+2 -1
View File
@@ -16,5 +16,6 @@ Last updated: 2026-09-12
| [`SacsMobileMaui`](../SacsMobileMaui/docs/component-reviews.md) | Project | .NET 10 migration complete; tracked in project docs | 2026-09-11 | Validate Apple targets on a Mac and continue API cleanup in the project tracker. | The shared project and both active mobile consumers target .NET 10. Windows runtime smoke tests pass, and Windows and Android Debug builds complete with zero errors. | | [`SacsMobileMaui`](../SacsMobileMaui/docs/component-reviews.md) | Project | .NET 10 migration complete; tracked in project docs | 2026-09-11 | Validate Apple targets on a Mac and continue API cleanup in the project tracker. | The shared project and both active mobile consumers target .NET 10. Windows runtime smoke tests pass, and Windows and Android Debug builds complete with zero errors. |
| `SacsStandardBase/Base/Procs/SaProcess_Base.cs` | Class | In progress | 2026-09-09 | Complete the Process Base actions recorded in `topics.md`; review derived processes separately for cooperative polling. | Lifecycle state ownership and cancellation lock ordering were changed. Five focused tests pass. Remaining concerns include mixed sync/async execution, durable cancellation outcome, cancellation exception handling, disposal, and status synchronization. | | `SacsStandardBase/Base/Procs/SaProcess_Base.cs` | Class | In progress | 2026-09-09 | Complete the Process Base actions recorded in `topics.md`; review derived processes separately for cooperative polling. | Lifecycle state ownership and cancellation lock ordering were changed. Five focused tests pass. Remaining concerns include mixed sync/async execution, durable cancellation outcome, cancellation exception handling, disposal, and status synchronization. |
| `SacsStandardBase/Base/Email/SaEmailConsole.cs` | Class | Dependency upgrade complete; security action still required | 2026-09-12 | Separately restore SMTP certificate validation and require the configured TLS mode. | MailKit and MimeKit 4.17.0 pass the Common and downstream validation suites without vulnerability warnings. Message construction is covered for addressing, bodies, attachments, content IDs, default filenames, and tags. The current SMTP client still accepts every server certificate. | | `SacsStandardBase/Base/Email/SaEmailConsole.cs` | Class | Dependency upgrade complete; security action still required | 2026-09-12 | Separately restore SMTP certificate validation and require the configured TLS mode. | MailKit and MimeKit 4.17.0 pass the Common and downstream validation suites without vulnerability warnings. Message construction is covered for addressing, bodies, attachments, content IDs, default filenames, and tags. The current SMTP client still accepts every server certificate. |
| `SacsStandardProc/Proc/Excel/SaMsSpreadsheet_POI.cs` | Class | Characterization baseline complete; dependency upgrade pending | 2026-09-12 | Upgrade to NPOI 2.7.6 with an explicit patched cryptography dependency and rerun the focused and downstream suites. | Ten tests exercise lifecycle, worksheets, XLSX byte/stream/file round trips, typed values, formulas, dimensions, active cells, and legacy XLS. Automatic worksheet naming, typed text reads, and XLS fallback stream rewinding were corrected; all specifications now pass. NPOI 2.8.0 introduces a maintenance-fee EULA; 2.7.6 remains Apache-2.0. | | `SacsStandardProc/Proc/Excel/SaMsSpreadsheet_POI.cs` | Class | Dependency upgrade complete | 2026-09-12 | Retain the focused spreadsheet suite for future NPOI updates; do not adopt NPOI 2.8.0 without an explicit licensing decision. | NPOI 2.7.6 is paired with an explicit `System.Security.Cryptography.Xml` 10.0.12 reference and NuGet reports no vulnerable packages for `SacsStandardProc`. Nullable date and indexed-colour API changes were adapted. Ten focused tests and all 111 Common tests pass; BusinessMgr builds and its 21 tests pass. |
| `SacsStandardBase/Base/Helpers/SacsEncryptionHelper.cs` and `SacsStandardBase/Base/Data/SacsAesIvCrypter.cs` | Classes | Reviewed; action required | 2026-09-08 | Design a versioned authenticated-encryption format with legacy migration and tamper tests. | Current CBC encryption is unauthenticated; password-based key derivation uses a fixed salt and low iteration count. | | `SacsStandardBase/Base/Helpers/SacsEncryptionHelper.cs` and `SacsStandardBase/Base/Data/SacsAesIvCrypter.cs` | Classes | Reviewed; action required | 2026-09-08 | Design a versioned authenticated-encryption format with legacy migration and tamper tests. | Current CBC encryption is unauthenticated; password-based key derivation uses a fixed salt and low iteration count. |
| `eng/validate.ps1` | Repository validation | Complete; passing | 2026-09-12 | Use as the standard validation entry point and extend it when repository-wide checks change. | Builds serially, runs 10 focused and 111 full tests, audits the NPOI-owning project, verifies changed-file line endings and diffs, then invokes BusinessMgr validation. |
+2 -1
View File
@@ -13,7 +13,6 @@ Last updated: 2026-09-12
| Priority | Topic | Date noted | Next step | Notes | | Priority | Topic | Date noted | Next step | Notes |
|---|---|---|---|---| |---|---|---|---|---|
| P1 | Upgrade NPOI and override its vulnerable cryptography dependency | 2026-09-12 | Evaluate NPOI 2.7.6 with an explicit `System.Security.Cryptography.Xml` 10.0.12 reference against the completed spreadsheet baseline, then validate downstream BusinessMgr exports. | `SacsStandardProc` directly references NPOI 2.6.2. Current NPOI 2.8.0 still declares vulnerable `System.Security.Cryptography.Xml` 8.0.2 and introduces a maintenance-fee EULA; do not adopt it without an explicit licensing decision. NPOI 2.7.6 remains Apache-2.0 but also needs the explicit cryptography override. Ten new NPOI tests pass, including fixes for automatic worksheet naming, typed reads of text cells, and XLS fallback stream handling. |
| P2 | Resolve .NET 10 migration warnings in non-MAUI Common code | 2026-09-10 | Replace the obsolete `Rfc2898DeriveBytes` constructor, make the three `FileStream.Read` operations exact, and review removal of the redundant `Microsoft.Win32.Registry` package. | The .NET 10 build reports `SYSLIB0060`, three `CA2022` warnings, and `NU1510`. The cryptography change overlaps the existing encryption-hardening topic and requires compatibility tests. | | P2 | Resolve .NET 10 migration warnings in non-MAUI Common code | 2026-09-10 | Replace the obsolete `Rfc2898DeriveBytes` constructor, make the three `FileStream.Read` operations exact, and review removal of the redundant `Microsoft.Win32.Registry` package. | The .NET 10 build reports `SYSLIB0060`, three `CA2022` warnings, and `NU1510`. The cryptography change overlaps the existing encryption-hardening topic and requires compatibility tests. |
| P1 | Prevent synchronous and asynchronous execution from overlapping in `SaProcess_Base` | 2026-09-09 | Route `RunProcess()` and `RunAsync()` through one guarded start operation and extend the lifecycle tests. | A synchronous call can currently reuse and dispose the asynchronous worker's task data. | | P1 | Prevent synchronous and asynchronous execution from overlapping in `SaProcess_Base` | 2026-09-09 | Route `RunProcess()` and `RunAsync()` through one guarded start operation and extend the lifecycle tests. | A synchronous call can currently reuse and dispose the asynchronous worker's task data. |
| P2 | Retain cancellation as a completed process outcome | 2026-09-09 | Store a durable cancellation result until the next process reset and test post-completion state. | `IsInterrupted` currently loses token-based cancellation after task-data cleanup. | | P2 | Retain cancellation as a completed process outcome | 2026-09-09 | Store a durable cancellation result until the next process reset and test post-completion state. | `IsInterrupted` currently loses token-based cancellation after task-data cleanup. |
@@ -54,3 +53,5 @@ Last updated: 2026-09-12
| Prepare Common libraries for the coordinated .NET 10 migration | 2026-09-10 | 2026-09-10 | 2026-09-10 | `SacsStandardBase`, `SacsStandardDbas`, `SacsStandardProc`, `SacsCoreWeb`, `SacsStandardAccounts`, and `SacsCoreUnitTests` now target `net8.0;net10.0`. The supported solution builds both targets with no errors; 94 of 94 tests pass on each target. BusinessMgr continues to build successfully on .NET 8 against the temporary compatible outputs. New .NET 10 warnings are tracked separately. | | Prepare Common libraries for the coordinated .NET 10 migration | 2026-09-10 | 2026-09-10 | 2026-09-10 | `SacsStandardBase`, `SacsStandardDbas`, `SacsStandardProc`, `SacsCoreWeb`, `SacsStandardAccounts`, and `SacsCoreUnitTests` now target `net8.0;net10.0`. The supported solution builds both targets with no errors; 94 of 94 tests pass on each target. BusinessMgr continues to build successfully on .NET 8 against the temporary compatible outputs. New .NET 10 warnings are tracked separately. |
| Complete the Common .NET 10 migration | 2026-09-10 | 2026-09-11 | 2026-09-11 | Removed the temporary .NET 8 targets from all supported non-MAUI libraries and tests. The full supported solution restores and builds with zero errors, and 94 of 94 tests pass on .NET 10. BusinessMgr rebuilds successfully with 8 of 8 Control and 13 of 13 Portal tests passing; Sandtrap and Sandalwood build successfully for Windows and Android. The isolated Xamarin reference project remains unchanged on .NET 8. | | Complete the Common .NET 10 migration | 2026-09-10 | 2026-09-11 | 2026-09-11 | Removed the temporary .NET 8 targets from all supported non-MAUI libraries and tests. The full supported solution restores and builds with zero errors, and 94 of 94 tests pass on .NET 10. BusinessMgr rebuilds successfully with 8 of 8 Control and 13 of 13 Portal tests passing; Sandtrap and Sandalwood build successfully for Windows and Android. The isolated Xamarin reference project remains unchanged on .NET 8. |
| Update vulnerable MailKit and MimeKit dependencies | 2026-09-10 | 2026-09-11 | 2026-09-12 | Updated the direct MailKit dependency from 4.13.0 to 4.17.0, which resolves MimeKit 4.17.0. NuGet reports no vulnerable packages for `SacsStandardBase`; the Common solution builds with zero errors and 101 of 101 tests pass. BusinessMgr builds with 8 of 8 Control and 13 of 13 Portal tests passing; Sandtrap and Sandalwood build successfully for Windows and Android. | | Update vulnerable MailKit and MimeKit dependencies | 2026-09-10 | 2026-09-11 | 2026-09-12 | Updated the direct MailKit dependency from 4.13.0 to 4.17.0, which resolves MimeKit 4.17.0. NuGet reports no vulnerable packages for `SacsStandardBase`; the Common solution builds with zero errors and 101 of 101 tests pass. BusinessMgr builds with 8 of 8 Control and 13 of 13 Portal tests passing; Sandtrap and Sandalwood build successfully for Windows and Android. |
| Upgrade NPOI and override its vulnerable cryptography dependency | 2026-09-12 | 2026-09-12 | 2026-09-12 | Updated NPOI from 2.6.2 to Apache-2.0 version 2.7.6 and added an explicit `System.Security.Cryptography.Xml` 10.0.12 reference. Adapted nullable date and indexed-colour APIs without changing the public wrapper behavior. NuGet reports no vulnerable packages for `SacsStandardProc`; all 10 spreadsheet tests and all 111 Common tests pass. The Common solution and BusinessMgr build successfully, with 8 of 8 Control and 13 of 13 Portal tests passing. BusinessMgr's separate vulnerable cryptography dependency through ASP.NET Core Identity 2.3.1 remains tracked in that repository. |
| Automate Common and downstream validation | 2026-09-12 | 2026-09-12 | 2026-09-12 | Added `eng/validate.ps1` as the repository-owned validation entry point. It performs a serial Common build, focused and full tests, the NPOI package audit, diff and changed-file line-ending checks, and downstream BusinessMgr validation. The complete workflow passes. |
+11
View File
@@ -0,0 +1,11 @@
# Validation
Run the complete Common validation, including the downstream BusinessMgr build and tests:
```powershell
.\eng\validate.ps1
```
Use `-SkipRestore` when packages are already restored. Use `-SkipDownstream` only for a deliberately scoped local check; shared behavior, public API, framework, and package changes require downstream validation.
The script suppresses .NET first-run setup and banners, builds serially to avoid excessive build-worker creation, stores test output under the ignored `TestResults/Validation` folder, audits the NPOI-owning project for vulnerable packages, checks the Git diff, and verifies working-tree line endings without modifying files.
+106
View File
@@ -0,0 +1,106 @@
[CmdletBinding()]
param(
[switch] $SkipRestore,
[switch] $SkipDownstream
)
$ErrorActionPreference = 'Stop'
$repositoryRoot = Split-Path -Parent $PSScriptRoot
$testResultsPath = Join-Path $repositoryRoot 'TestResults\Validation'
$dotnetHome = Join-Path (Split-Path -Parent $repositoryRoot) '.dotnet-cli-validation'
$env:DOTNET_CLI_HOME = $dotnetHome
$env:DOTNET_GENERATE_ASPNET_CERTIFICATE = 'false'
$env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = '1'
$env:DOTNET_CLI_TELEMETRY_OPTOUT = '1'
$env:DOTNET_NOLOGO = '1'
function Invoke-External
{
param(
[Parameter(Mandatory)] [string] $FilePath,
[Parameter(Mandatory)] [string[]] $ArgumentList
)
& $FilePath @ArgumentList
if ($LASTEXITCODE -ne 0)
{
throw "Command failed with exit code ${LASTEXITCODE}: $FilePath $($ArgumentList -join ' ')"
}
}
function Test-LineEndings
{
$crlfExtensions = @('.cs', '.csproj', '.sln', '.props', '.targets', '.config', '.json', '.xml', '.md', '.txt', '.ps1', '.cmd', '.bat')
$lfExtensions = @('.svg', '.html', '.css', '.js', '.sh', '.yml', '.yaml')
$invalidFiles = [System.Collections.Generic.List[string]]::new()
$files = @(& git -C $repositoryRoot diff --name-only --diff-filter=ACMRT)
if ($LASTEXITCODE -ne 0) { throw 'Unable to enumerate modified repository files.' }
$files += @(& git -C $repositoryRoot diff --cached --name-only --diff-filter=ACMRT)
if ($LASTEXITCODE -ne 0) { throw 'Unable to enumerate staged repository files.' }
$files += @(& git -C $repositoryRoot ls-files --others --exclude-standard)
if ($LASTEXITCODE -ne 0) { throw 'Unable to enumerate untracked repository files.' }
$files = $files | Sort-Object -Unique
foreach ($relativePath in $files)
{
$extension = [System.IO.Path]::GetExtension($relativePath).ToLowerInvariant()
if (($extension -notin $crlfExtensions) -and ($extension -notin $lfExtensions)) { continue }
$fullPath = Join-Path $repositoryRoot $relativePath
if (-not (Test-Path -LiteralPath $fullPath -PathType Leaf)) { continue }
$bytes = [System.IO.File]::ReadAllBytes($fullPath)
for ($index = 0; $index -lt $bytes.Length; $index++)
{
if (($extension -in $crlfExtensions) -and ($bytes[$index] -eq 10) -and (($index -eq 0) -or ($bytes[$index - 1] -ne 13)))
{
$invalidFiles.Add($relativePath)
break
}
if (($extension -in $lfExtensions) -and ($bytes[$index] -eq 13))
{
$invalidFiles.Add($relativePath)
break
}
}
}
if ($invalidFiles.Count -gt 0)
{
throw "Files with incorrect line endings:`n$($invalidFiles -join "`n")"
}
}
Push-Location $repositoryRoot
try
{
Write-Host 'Validating Common workspace...'
if (-not $SkipRestore)
{
Invoke-External dotnet @('restore', 'SaCommonVS.sln', '--disable-build-servers', '--verbosity', 'minimal')
}
Invoke-External dotnet @('build', 'SaCommonVS.sln', '--configuration', 'Debug', '--no-restore', '--disable-build-servers', '--verbosity', 'minimal', '--maxcpucount:1')
Invoke-External dotnet @('test', 'SacsCoreUnitTests\SacsCoreUnitTests.csproj', '--configuration', 'Debug', '--no-build', '--no-restore', '--filter', 'FullyQualifiedName~SaMsSpreadsheet_POI_Tester', '--results-directory', $testResultsPath, '--verbosity', 'minimal')
Invoke-External dotnet @('test', 'SacsCoreUnitTests\SacsCoreUnitTests.csproj', '--configuration', 'Debug', '--no-build', '--no-restore', '--results-directory', $testResultsPath, '--verbosity', 'minimal')
Invoke-External dotnet @('list', 'SacsStandardProc\SacsStandardProc.csproj', 'package', '--vulnerable', '--include-transitive')
Invoke-External git @('diff', '--check')
Test-LineEndings
if (-not $SkipDownstream)
{
$businessMgrValidation = Join-Path $repositoryRoot '..\..\BusinessMgr\Workspace\eng\validate.ps1'
if (-not (Test-Path -LiteralPath $businessMgrValidation -PathType Leaf))
{
throw "BusinessMgr validation script was not found at $businessMgrValidation"
}
& $businessMgrValidation
if (-not $?) { throw 'BusinessMgr validation failed.' }
}
Write-Host 'Common validation passed.' -ForegroundColor Green
}
finally
{
Pop-Location
}