firefly-import-preprocessor/tests/ColumnTransformerTest.php
Reindl David (IT-PTR-CEN2-SL10) 170b2d2016 release 1.0
2026-05-02 17:53:19 +02:00

508 lines
17 KiB
PHP

<?php
namespace UbsCsvTransformer\Tests;
use PHPUnit\Framework\TestCase;
use UbsCsvTransformer\ColumnTransformer;
use UbsCsvTransformer\DebugLogger;
class ColumnTransformerTest extends TestCase
{
protected function setUp(): void
{
DebugLogger::reset();
}
/**
* Helper: build a transformer with one rule and apply it to the given row.
*/
private function applyOne(array $config, array $row, array $metadata = []): array
{
return (new ColumnTransformer([$config], $metadata))->transformRow($row);
}
// -------------------------------------------------------------------------
// map
// -------------------------------------------------------------------------
public function testMapPassthrough(): void
{
$result = $this->applyOne(
['sourceColumn' => 'Name', 'outputColumn' => 'Name', 'type' => 'map'],
['Name' => 'Alice']
);
$this->assertSame('Alice', $result['Name']);
}
// -------------------------------------------------------------------------
// replace
// -------------------------------------------------------------------------
public function testReplace(): void
{
$result = $this->applyOne([
'sourceColumn' => 'Col',
'outputColumn' => 'Col',
'type' => 'replace',
'search' => 'foo',
'replace' => 'bar',
], ['Col' => 'foo baz foo']);
$this->assertSame('bar baz bar', $result['Col']);
}
public function testReplaceEmptySearchReturnsOriginal(): void
{
$result = $this->applyOne([
'sourceColumn' => 'Col',
'outputColumn' => 'Col',
'type' => 'replace',
'search' => '',
'replace' => 'bar',
], ['Col' => 'hello']);
$this->assertSame('hello', $result['Col']);
}
// -------------------------------------------------------------------------
// dateformat
// -------------------------------------------------------------------------
public function testDateFormat(): void
{
$result = $this->applyOne([
'sourceColumn' => 'Date',
'outputColumn' => 'Date',
'type' => 'dateformat',
'fromFormat' => 'd.m.Y',
'toFormat' => 'Y-m-d',
], ['Date' => '15.03.2024']);
$this->assertSame('2024-03-15', $result['Date']);
}
public function testDateFormatInvalidValueReturnsOriginal(): void
{
$result = $this->applyOne([
'sourceColumn' => 'Date',
'outputColumn' => 'Date',
'type' => 'dateformat',
'fromFormat' => 'd.m.Y',
'toFormat' => 'Y-m-d',
], ['Date' => 'not-a-date']);
$this->assertSame('not-a-date', $result['Date']);
}
public function testDateFormatEmptyValueReturnsEmpty(): void
{
$result = $this->applyOne([
'sourceColumn' => 'Date',
'outputColumn' => 'Date',
'type' => 'dateformat',
'fromFormat' => 'd.m.Y',
'toFormat' => 'Y-m-d',
], ['Date' => '']);
$this->assertSame('', $result['Date']);
}
// -------------------------------------------------------------------------
// split
// -------------------------------------------------------------------------
public function testSplitPart0(): void
{
$result = $this->applyOne([
'sourceColumn' => 'Col',
'outputColumn' => 'Col',
'type' => 'split',
'delimiter' => ';',
'part' => 0,
], ['Col' => 'Coop Pronto;7007 Chur']);
$this->assertSame('Coop Pronto', $result['Col']);
}
public function testSplitPart1(): void
{
$result = $this->applyOne([
'sourceColumn' => 'Col',
'outputColumn' => 'Col',
'type' => 'split',
'delimiter' => ';',
'part' => 1,
], ['Col' => 'Coop Pronto;7007 Chur']);
$this->assertSame('7007 Chur', $result['Col']);
}
public function testSplitPartOutOfBoundsReturnsOriginal(): void
{
$result = $this->applyOne([
'sourceColumn' => 'Col',
'outputColumn' => 'Col',
'type' => 'split',
'delimiter' => ';',
'part' => 5,
], ['Col' => 'A;B']);
$this->assertSame('A;B', $result['Col']);
}
// -------------------------------------------------------------------------
// regexextract
// -------------------------------------------------------------------------
public function testRegexExtract(): void
{
$result = $this->applyOne([
'sourceColumn' => 'Col',
'outputColumn' => 'Zip',
'type' => 'regexextract',
'pattern' => '(\d{4})',
], ['Col' => 'Shop 7007 Chur', 'Zip' => '']);
$this->assertSame('7007', $result['Zip']);
}
public function testRegexExtractNoMatchReturnsEmpty(): void
{
$result = $this->applyOne([
'sourceColumn' => 'Col',
'outputColumn' => 'Zip',
'type' => 'regexextract',
'pattern' => '(\d{4})',
], ['Col' => 'No digits here', 'Zip' => '']);
$this->assertSame('', $result['Zip']);
}
public function testRegexExtractEmptyValueReturnsEmpty(): void
{
$result = $this->applyOne([
'sourceColumn' => 'Col',
'outputColumn' => 'Zip',
'type' => 'regexextract',
'pattern' => '(\d{4})',
], ['Col' => '', 'Zip' => '']);
$this->assertSame('', $result['Zip']);
}
// -------------------------------------------------------------------------
// trim
// -------------------------------------------------------------------------
public function testTrim(): void
{
$result = $this->applyOne(
['sourceColumn' => 'Col', 'outputColumn' => 'Col', 'type' => 'trim'],
['Col' => ' hello world ']
);
$this->assertSame('hello world', $result['Col']);
}
// -------------------------------------------------------------------------
// uppercase
// -------------------------------------------------------------------------
public function testUppercase(): void
{
$result = $this->applyOne(
['sourceColumn' => 'Col', 'outputColumn' => 'Col', 'type' => 'uppercase'],
['Col' => 'Hello World']
);
$this->assertSame('HELLO WORLD', $result['Col']);
}
public function testUppercaseUnicode(): void
{
$result = $this->applyOne(
['sourceColumn' => 'Col', 'outputColumn' => 'Col', 'type' => 'uppercase'],
['Col' => 'zürich']
);
$this->assertSame('ZÜRICH', $result['Col']);
}
// -------------------------------------------------------------------------
// lowercase
// -------------------------------------------------------------------------
public function testLowercase(): void
{
$result = $this->applyOne(
['sourceColumn' => 'Col', 'outputColumn' => 'Col', 'type' => 'lowercase'],
['Col' => 'Hello World']
);
$this->assertSame('hello world', $result['Col']);
}
// -------------------------------------------------------------------------
// ucwordsfirst
// -------------------------------------------------------------------------
public function testUcwordsFirst(): void
{
$result = $this->applyOne(
['sourceColumn' => 'Col', 'outputColumn' => 'Col', 'type' => 'ucwordsfirst'],
['Col' => 'COOP PRONTO CHUR']
);
$this->assertSame('Coop Pronto Chur', $result['Col']);
}
public function testUcwordsFirstHyphen(): void
{
$result = $this->applyOne(
['sourceColumn' => 'Col', 'outputColumn' => 'Col', 'type' => 'ucwordsfirst'],
['Col' => 'SAINT-JEAN-DE-MAURIENNE']
);
$this->assertSame('Saint-Jean-De-Maurienne', $result['Col']);
}
public function testUcwordsFirstApostrophe(): void
{
$result = $this->applyOne(
['sourceColumn' => 'Col', 'outputColumn' => 'Col', 'type' => 'ucwordsfirst'],
['Col' => "O'NEILL STORE"]
);
$this->assertSame("O'Neill Store", $result['Col']);
}
// -------------------------------------------------------------------------
// truncate
// -------------------------------------------------------------------------
public function testTruncate(): void
{
$result = $this->applyOne([
'sourceColumn' => 'Col',
'outputColumn' => 'Col',
'type' => 'truncate',
'maxLength' => 5,
], ['Col' => 'Hello World']);
$this->assertSame('Hello', $result['Col']);
}
public function testTruncateShorterThanMaxIsUnchanged(): void
{
$result = $this->applyOne([
'sourceColumn' => 'Col',
'outputColumn' => 'Col',
'type' => 'truncate',
'maxLength' => 100,
], ['Col' => 'Short']);
$this->assertSame('Short', $result['Col']);
}
public function testTruncateUnicode(): void
{
// 'ü' counts as 1 Unicode character, so maxLength=3 gives 3 chars: Z, ü, r
$result = $this->applyOne([
'sourceColumn' => 'Col',
'outputColumn' => 'Col',
'type' => 'truncate',
'maxLength' => 3,
], ['Col' => 'Zürich']);
$this->assertSame('Zür', $result['Col']);
}
// -------------------------------------------------------------------------
// constantvalue
// -------------------------------------------------------------------------
public function testConstantValue(): void
{
$transformer = new ColumnTransformer([[
'sourceColumn' => '_constant_',
'outputColumn' => 'Currency',
'type' => 'constantvalue',
'metadataKey' => 'currency_code',
]], ['currency_code' => 'CHF']);
$result = $transformer->transformRow(['Currency' => '']);
$this->assertSame('CHF', $result['Currency']);
}
public function testConstantValueMissingKeyReturnsEmpty(): void
{
$transformer = new ColumnTransformer([[
'sourceColumn' => '_constant_',
'outputColumn' => 'Currency',
'type' => 'constantvalue',
'metadataKey' => 'nonexistent',
]], []);
$result = $transformer->transformRow(['Currency' => '']);
$this->assertSame('', $result['Currency']);
}
// -------------------------------------------------------------------------
// pipeline
// -------------------------------------------------------------------------
public function testPipeline(): void
{
$result = $this->applyOne([
'sourceColumn' => 'Col',
'outputColumn' => 'Col',
'type' => 'pipeline',
'steps' => [
['type' => 'trim'],
['type' => 'lowercase'],
['type' => 'ucwordsfirst'],
],
], ['Col' => ' COOP PRONTO ']);
$this->assertSame('Coop Pronto', $result['Col']);
}
public function testPipelineEmptyStepsReturnsOriginal(): void
{
$result = $this->applyOne([
'sourceColumn' => 'Col',
'outputColumn' => 'Col',
'type' => 'pipeline',
'steps' => [],
], ['Col' => 'hello']);
$this->assertSame('hello', $result['Col']);
}
// -------------------------------------------------------------------------
// Inline transformations[] array (flat pipeline per column entry)
// -------------------------------------------------------------------------
public function testInlineTransformationsArray(): void
{
$result = $this->applyOne([
'sourceColumn' => 'Col',
'outputColumn' => 'Col',
'type' => 'map',
'transformations' => [
['type' => 'trim'],
['type' => 'uppercase'],
],
], ['Col' => ' hello ']);
$this->assertSame('HELLO', $result['Col']);
}
// -------------------------------------------------------------------------
// normalizeTransformType: snake_case and kebab-case aliases
// -------------------------------------------------------------------------
public function testNormalizeTypeSnakeCase(): void
{
$result = $this->applyOne([
'sourceColumn' => 'Date',
'outputColumn' => 'Date',
'type' => 'date_format',
'fromFormat' => 'd.m.Y',
'toFormat' => 'Y-m-d',
], ['Date' => '15.03.2024']);
$this->assertSame('2024-03-15', $result['Date']);
}
public function testNormalizeTypeKebabCase(): void
{
$result = $this->applyOne([
'sourceColumn' => 'Col',
'outputColumn' => 'Col',
'type' => 'ucwords-first',
], ['Col' => 'HELLO WORLD']);
$this->assertSame('Hello World', $result['Col']);
}
// -------------------------------------------------------------------------
// outputAction
// -------------------------------------------------------------------------
public function testOutputActionOverwrite(): void
{
$result = $this->applyOne([
'sourceColumn' => 'A',
'outputColumn' => 'B',
'type' => 'map',
'outputAction' => 'overwrite',
], ['A' => 'new', 'B' => 'old']);
$this->assertSame('new', $result['B']);
}
public function testOutputActionCreate(): void
{
$result = $this->applyOne([
'sourceColumn' => 'A',
'outputColumn' => 'NewCol',
'type' => 'map',
'outputAction' => 'create',
], ['A' => 'hello']);
$this->assertSame('hello', $result['NewCol']);
}
public function testOutputActionAppend(): void
{
$result = $this->applyOne([
'sourceColumn' => 'A',
'outputColumn' => 'B',
'type' => 'map',
'outputAction' => 'append',
], ['A' => ' World', 'B' => 'Hello']);
$this->assertSame('Hello World', $result['B']);
}
// -------------------------------------------------------------------------
// multi-output split
// -------------------------------------------------------------------------
public function testMultiOutputSplit(): void
{
$transformer = new ColumnTransformer([[
'outputs' => ['FirstName', 'LastName'],
'sourceColumn' => 'FullName',
'type' => 'split',
'delimiter' => ' ',
]]);
$result = $transformer->transformRow(['FullName' => 'John Doe']);
$this->assertSame('John', $result['FirstName']);
$this->assertSame('Doe', $result['LastName']);
}
public function testMultiOutputSplitFewerPartsYieldsEmptyString(): void
{
$transformer = new ColumnTransformer([[
'outputs' => ['Col1', 'Col2', 'Col3'],
'sourceColumn' => 'Source',
'type' => 'split',
'delimiter' => ';',
]]);
$result = $transformer->transformRow(['Source' => 'A;B']);
$this->assertSame('A', $result['Col1']);
$this->assertSame('B', $result['Col2']);
$this->assertSame('', $result['Col3']);
}
// -------------------------------------------------------------------------
// Error cases
// -------------------------------------------------------------------------
public function testMissingOutputColumnThrows(): void
{
$this->expectException(\RuntimeException::class);
$transformer = new ColumnTransformer([
['sourceColumn' => 'A', 'type' => 'map'],
]);
$transformer->transformRow(['A' => 'x']);
}
public function testMultiOutputNonSplitTypeThrows(): void
{
$this->expectException(\RuntimeException::class);
$transformer = new ColumnTransformer([[
'outputs' => ['Col1', 'Col2'],
'sourceColumn' => 'Source',
'type' => 'uppercase',
]]);
$transformer->transformRow(['Source' => 'hello']);
}
// -------------------------------------------------------------------------
// getOutputColumns
// -------------------------------------------------------------------------
public function testGetOutputColumnsCountsUniqueColumns(): void
{
$transformer = new ColumnTransformer([
['sourceColumn' => 'A', 'outputColumn' => 'X', 'type' => 'map'],
['sourceColumn' => 'B', 'outputColumn' => 'Y', 'type' => 'map'],
['sourceColumn' => 'C', 'outputColumn' => 'X', 'type' => 'map'], // duplicate output
]);
$transformer->transformRow(['A' => '1', 'B' => '2', 'C' => '3']);
$this->assertSame(2, $transformer->getOutputColumns());
}
}