From 7024a59b22f0499975353630a6af0b41d1d47ab9 Mon Sep 17 00:00:00 2001 From: Claudear <262350598+claudear@users.noreply.github.com> Date: Wed, 11 Mar 2026 11:45:13 +0000 Subject: [PATCH] Downgrade missing required CSV column from exception to warning When a CSV file is missing columns marked as required in the database schema, the import now logs a warning instead of throwing an exception. This allows the import to proceed and lets the database layer enforce constraints per-row, rather than blocking the entire import. Co-Authored-By: Claude Opus 4.6 --- src/Migration/Sources/CSV.php | 12 +++-- tests/Migration/Unit/General/CSVTest.php | 56 ++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 7 deletions(-) diff --git a/src/Migration/Sources/CSV.php b/src/Migration/Sources/CSV.php index 7d02090f..36ad1975 100644 --- a/src/Migration/Sources/CSV.php +++ b/src/Migration/Sources/CSV.php @@ -479,26 +479,24 @@ private function validateCSVHeaders(array $headers, array $columnTypes, array $r $messages = []; - // If there are missing required columns, throw an exception + // If there are missing required columns, log a warning (the database layer will enforce constraints per-row) if (!empty($missingRequired)) { $label = \count($missingRequired) === 1 ? 'Missing required column' : 'Missing required columns'; $messages[] = "$label: '" . \implode("', '", $missingRequired) . "'"; } - if (!empty($missingRequired)) { - throw new \Exception('CSV header validation failed: ' . \implode('. ', $messages), Exception::CODE_VALIDATION); - } - // If there are unknown columns but no missing required columns, just log a warning + // If there are unknown columns, log a warning $unknown = \array_diff($headers, $allKnown, $internals); if (!empty($unknown)) { $label = \count($unknown) === 1 ? 'Unknown column' : 'Unknown columns'; $messages[] = "$label: '" . \implode("', '", $unknown) . "' (will be ignored)"; } - if (!empty($unknown)) { + + if (!empty($messages)) { $this->addWarning(new Warning( UtopiaResource::TYPE_ROW, Transfer::GROUP_DATABASES, - \implode(', ', $messages), + \implode('. ', $messages), $this->resourceId )); } diff --git a/tests/Migration/Unit/General/CSVTest.php b/tests/Migration/Unit/General/CSVTest.php index e4fd3ec1..43bd7aee 100644 --- a/tests/Migration/Unit/General/CSVTest.php +++ b/tests/Migration/Unit/General/CSVTest.php @@ -416,6 +416,62 @@ public function testCSVExportImportCompatibility() } } + /** + * @throws \ReflectionException + */ + public function testValidateCSVHeadersMissingRequiredColumnIsWarning(): void + { + $reflection = new \ReflectionClass(CSV::class); + $instance = $reflection->newInstanceWithoutConstructor(); + + // Set resourceId to avoid uninitialized property error + $resourceIdProp = $reflection->getProperty('resourceId'); + $resourceIdProp->setAccessible(true); + $resourceIdProp->setValue($instance, 'testdb:testtable'); + + $refMethod = $reflection->getMethod('validateCSVHeaders'); + $refMethod->setAccessible(true); + + $headers = ['name', 'age']; + $columnTypes = ['name' => 'string', 'age' => 'integer', 'texte' => 'string']; + $requiredColumns = ['texte' => true]; + + // Should NOT throw an exception — missing required columns should be a warning + $refMethod->invoke($instance, $headers, $columnTypes, $requiredColumns); + + // Verify a warning was added + $this->assertNotEmpty($instance->warnings, 'A warning should be added for missing required columns'); + $this->assertStringContainsString('texte', $instance->warnings[0]->getMessage()); + } + + /** + * @throws \ReflectionException + */ + public function testValidateCSVHeadersAllRequiredPresent(): void + { + $reflection = new \ReflectionClass(CSV::class); + $instance = $reflection->newInstanceWithoutConstructor(); + + $refMethod = $reflection->getMethod('validateCSVHeaders'); + $refMethod->setAccessible(true); + + $headers = ['name', 'age', 'texte']; + $columnTypes = ['name' => 'string', 'age' => 'integer', 'texte' => 'string']; + $requiredColumns = ['texte' => true]; + + // Should not throw and not add warnings for required columns + $refMethod->invoke($instance, $headers, $columnTypes, $requiredColumns); + + // No warnings about missing required columns (may have unknown column warnings) + $hasRequiredWarning = false; + foreach ($instance->warnings as $warning) { + if (str_contains($warning->getMessage(), 'Missing required')) { + $hasRequiredWarning = true; + } + } + $this->assertFalse($hasRequiredWarning, 'No warning should be added when all required columns are present'); + } + private function recursiveDelete(string $dir): void { if (is_dir($dir)) {