Files
breadcrumb-the-shire/tests/Service/Export/ExportHelpersTest.php

47 lines
1.5 KiB
PHP
Raw Normal View History

feat(core): generic CSV export primitive + helpdesk domains consumer Add a reusable CSV-export building block to the core so any Grid.js list page can gain a filter/sort-aware download with two clicks of glue. Core primitive: - core/Service/Export/CsvExportService: flavor-aware writer (plain CSV or Excel-compatible UTF-8+BOM+semicolon), with OWASP-aligned formula- injection escape (=, @, +, -, TAB, CR) applied to both rows *and* headers. Value objects for columns (ExportColumn) carry an extractor closure and an allowSignedNumeric flag for phone-number-shaped cells. - core/Support/helpers/export.php: thin HTTP layer (exportSendCsv, exportCapLimit, exportResolveFlavor, exportRequireGetRequest) reusing the requestInput() contract for GR-CORE-003 consistency. Filename is sanitized and length-clamped; callers must still enforce auth. - templates/partials/app-list-export-dropdown.phtml: zero-config <details class="dropdown"> with CSV + Excel triggers. - web/js/core/app-list-export.js: initListExport({ gridConfig, exportUrl }) mirrors the current grid filters + sort onto the export URL and navigates, preserving session cookies for download. First consumer — Helpdesk domains: - DomainListService extracts the shared filter/enrich/sort logic from domains-data so the grid endpoint and the new domains/export endpoint cannot drift. - domains/export endpoint delegates to DomainListService, declares ExportColumns (with translated level labels), and exits via exportSendCsv. - domains-data now ~20 lines, delegating to DomainListService. Tests: - CsvExportServiceTest (10 cases): both flavors, BOM/no-BOM, formula escapes incl. TAB/CR, header escape, signed-numeric allowlist, quoting, multiline, empty columns, generator-compatible iterable. - ExportHelpersTest (5 cases): exportCapLimit bounds. - DomainListServiceTest (8 cases): filter, search, "all" sentinel, sort, paging, enrichment, BC failure, tenant-scoped security filter. Gates: PHPUnit green, PHPStan clean on touched files, module:sync ok. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 21:57:19 +02:00
<?php
namespace MintyPHP\Tests\Service\Export;
use PHPUnit\Framework\TestCase;
/**
* Thin tests for the procedural helpers in core/Support/helpers/export.php.
* The functions are bootstrapped via composer autoload through
* core/Support/helpers.php, so they are globally available in tests.
*/
class ExportHelpersTest extends TestCase
{
public function testExportCapLimitClampsNonNumericToMax(): void
{
$this->assertSame(5000, exportCapLimit(null, 5000));
$this->assertSame(5000, exportCapLimit('', 5000));
$this->assertSame(5000, exportCapLimit('abc', 5000));
}
public function testExportCapLimitClampsZeroAndNegativeToMax(): void
{
$this->assertSame(5000, exportCapLimit(0, 5000));
$this->assertSame(5000, exportCapLimit(-1, 5000));
$this->assertSame(5000, exportCapLimit('-42', 5000));
}
public function testExportCapLimitRespectsUserValueWhenInRange(): void
{
$this->assertSame(250, exportCapLimit(250, 5000));
$this->assertSame(5000, exportCapLimit(5000, 5000));
$this->assertSame(1, exportCapLimit(1, 5000));
}
public function testExportCapLimitClampsAboveMaxToMax(): void
{
$this->assertSame(5000, exportCapLimit(9999, 5000));
$this->assertSame(100, exportCapLimit(999999, 100));
}
public function testExportCapLimitHonoursCustomMax(): void
{
$this->assertSame(50, exportCapLimit(50, 100));
$this->assertSame(100, exportCapLimit(200, 100));
}
}