From 33b7c3e51a0072ee11d96afad84f227b59c78bad Mon Sep 17 00:00:00 2001 From: Jeppe Bundgaard Date: Wed, 3 Jun 2026 19:06:09 +0200 Subject: [PATCH] Widen self-serve task descriptions --- .../classes/selfserve_schema_bootstrap.php | 59 ++++++++- .../Api/SelfserveCustomerStartApiTest.php | 44 +++++++ ...fserveSchemaBootstrapCompatibilityTest.php | 112 ++++++++++++++++++ 3 files changed, 214 insertions(+), 1 deletion(-) diff --git a/services/nginx/app/classes/selfserve_schema_bootstrap.php b/services/nginx/app/classes/selfserve_schema_bootstrap.php index d67b4f41..50ad00a4 100644 --- a/services/nginx/app/classes/selfserve_schema_bootstrap.php +++ b/services/nginx/app/classes/selfserve_schema_bootstrap.php @@ -95,7 +95,7 @@ class selfserve_schema_bootstrap session_id INT NOT NULL, task_id INT NULL, task_text VARCHAR(255) NOT NULL, - description VARCHAR(255) NULL, + description TEXT NULL, services JSON NULL, buttons JSON NULL, dynamic_image_id INT NULL, @@ -197,6 +197,18 @@ class selfserve_schema_bootstrap 'gate_ref_id', 'ALTER TABLE department_selfserve_tasks ADD COLUMN gate_ref_id INT NULL AFTER gate_type' ); + self::ensureColumnDataType( + 'department_selfserve_tasks', + 'description', + ['text', 'mediumtext', 'longtext'], + 'ALTER TABLE department_selfserve_tasks MODIFY COLUMN description TEXT NULL AFTER task' + ); + self::ensureColumnDataType( + 'selfserve_wash_session_tasks', + 'description', + ['text', 'mediumtext', 'longtext'], + 'ALTER TABLE selfserve_wash_session_tasks MODIFY COLUMN description TEXT NULL AFTER task_text' + ); self::ensureColumn( 'selfserve_wash_session_tasks', 'dynamic_images_vehicle_type', @@ -239,4 +251,49 @@ class selfserve_schema_bootstrap } $db->query($alterSql); } + + /** + * @param array $acceptedDataTypes + */ + public static function ensureColumnDataType(string $table, string $column, array $acceptedDataTypes, string $alterSql): void + { + global $db; + + $columnInfo = self::columnInfo($table, $column); + if ($columnInfo === null) { + return; + } + + $dataType = strtolower((string)($columnInfo['DATA_TYPE'] ?? '')); + $acceptedDataTypes = array_map(static fn(string $type): string => strtolower($type), $acceptedDataTypes); + if (in_array($dataType, $acceptedDataTypes, true)) { + return; + } + + $db->query($alterSql); + } + + /** + * @return array|null + */ + public static function columnInfo(string $table, string $column): ?array + { + global $db; + $table = $db->escape_string($table); + $column = $db->escape_string($column); + $database = $db->escape_string($db->getDatabase()); + + $sql = "SELECT DATA_TYPE, COLUMN_TYPE, IS_NULLABLE, CHARACTER_MAXIMUM_LENGTH + FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = '$database' + AND TABLE_NAME = '$table' + AND COLUMN_NAME = '$column' + LIMIT 1"; + $result = $db->query($sql); + if (!$result) { + return null; + } + $row = $result->fetch_assoc(); + return is_array($row) ? $row : null; + } } diff --git a/services/nginx/app/tests/Api/SelfserveCustomerStartApiTest.php b/services/nginx/app/tests/Api/SelfserveCustomerStartApiTest.php index 572919bf..0badb666 100644 --- a/services/nginx/app/tests/Api/SelfserveCustomerStartApiTest.php +++ b/services/nginx/app/tests/Api/SelfserveCustomerStartApiTest.php @@ -128,3 +128,47 @@ it('derives allowed services from v2 session task snapshots when task rows are n $sessionTask->delete(); } }); + +it('keeps long generated task descriptions when refreshing vehicle eligibility snapshots', function (): void { + $group = api_fixtures()->createGroup([], [ + 'list_own_department_selfserve_vehicle_conditions', + ]); + $scenario = api_fixtures()->createSelfServeScenario([ + 'customer' => ['group_id' => $group['id']], + 'department_selfserve_enabled' => true, + 'lane_selfserve_enabled' => true, + ]); + $headers = api_fixtures()->bearerHeaders( + api_fixtures()->createAuthToken((int)$scenario['customer']['id']) + ); + $laneId = (int)$scenario['lane']['id']; + $reg = (string)$scenario['vehicle']['reg']; + $productId = (int)$scenario['product']['id']; + $taskId = (int)$scenario['tasks'][1]['id']; + $longDescription = str_repeat('Hvis ja - vaelg tagboerste program (#6) og lift program (#2) / ', 6); + + $statement = api_test_runtime()->db()->prepare( + 'UPDATE department_selfserve_tasks SET description = ? WHERE id = ?' + ); + $statement->bind_param('si', $longDescription, $taskId); + $statement->execute(); + + api_client() + ->get( + '/department/selfserve/vehicle/allowed?lane_id=' . $laneId + . '®=' . urlencode($reg) + . '&vehicle_type=' . $productId, + $headers + ) + ->assertStatus(200) + ->assertSuccess(true); + + $sessionTask = api_test_runtime()->queryOne( + 'SELECT description FROM selfserve_wash_session_tasks' + . ' WHERE session_id = ' . (int)$scenario['session']['id'] + . ' AND task_id = ' . $taskId + . ' LIMIT 1' + ); + + expect($sessionTask['description'] ?? null)->toBe($longDescription); +}); diff --git a/services/nginx/app/tests/Unit/Selfserve/SelfserveSchemaBootstrapCompatibilityTest.php b/services/nginx/app/tests/Unit/Selfserve/SelfserveSchemaBootstrapCompatibilityTest.php index dc0f9776..b03cfce7 100644 --- a/services/nginx/app/tests/Unit/Selfserve/SelfserveSchemaBootstrapCompatibilityTest.php +++ b/services/nginx/app/tests/Unit/Selfserve/SelfserveSchemaBootstrapCompatibilityTest.php @@ -9,6 +9,118 @@ it('adds dynamic_images_vehicle_type column for legacy selfserve wash session ta expect($bootstrapContent)->toContain('ALTER TABLE selfserve_wash_session_tasks ADD COLUMN dynamic_images_vehicle_type INT NULL AFTER buttons'); }); +it('widens self-serve task descriptions for generated workbook instructions', function (): void { + $bootstrapContent = file_get_contents(app_path('classes/selfserve_schema_bootstrap.php')); + + expect($bootstrapContent)->not->toBeFalse(); + expect($bootstrapContent)->toContain('description TEXT NULL'); + expect($bootstrapContent)->toContain('ensureColumnDataType('); + expect($bootstrapContent)->toContain("['text', 'mediumtext', 'longtext']"); + expect($bootstrapContent)->toContain('ALTER TABLE department_selfserve_tasks MODIFY COLUMN description TEXT NULL AFTER task'); + expect($bootstrapContent)->toContain('ALTER TABLE selfserve_wash_session_tasks MODIFY COLUMN description TEXT NULL AFTER task_text'); +}); + +it('alters legacy bounded description columns to text', function (): void { + $hadDb = array_key_exists('db', $GLOBALS); + $previousDb = $GLOBALS['db'] ?? null; + + try { + foreach (['varchar', 'tinytext'] as $legacyType) { + $fakeDb = new class ($legacyType) { + public array $queries = []; + + public function __construct(private readonly string $dataType) + { + } + + public function escape_string(string $value): string + { + return addslashes($value); + } + + public function getDatabase(): string + { + return 'test_db'; + } + + public function query(string $sql): object|bool + { + $this->queries[] = $sql; + if (str_contains($sql, 'information_schema.COLUMNS')) { + return new class ($this->dataType) { + public function __construct(private readonly string $dataType) + { + } + + public function fetch_assoc(): array + { + return ['DATA_TYPE' => $this->dataType]; + } + }; + } + + return true; + } + }; + $GLOBALS['db'] = $fakeDb; + + \classes\selfserve_schema_bootstrap::ensureColumnDataType( + 'selfserve_wash_session_tasks', + 'description', + ['text', 'mediumtext', 'longtext'], + 'ALTER TABLE selfserve_wash_session_tasks MODIFY COLUMN description TEXT NULL AFTER task_text' + ); + + expect($fakeDb->queries)->toContain('ALTER TABLE selfserve_wash_session_tasks MODIFY COLUMN description TEXT NULL AFTER task_text'); + } + + $textDb = new class { + public array $queries = []; + + public function escape_string(string $value): string + { + return addslashes($value); + } + + public function getDatabase(): string + { + return 'test_db'; + } + + public function query(string $sql): object|bool + { + $this->queries[] = $sql; + if (str_contains($sql, 'information_schema.COLUMNS')) { + return new class { + public function fetch_assoc(): array + { + return ['DATA_TYPE' => 'text']; + } + }; + } + + return true; + } + }; + $GLOBALS['db'] = $textDb; + + \classes\selfserve_schema_bootstrap::ensureColumnDataType( + 'selfserve_wash_session_tasks', + 'description', + ['text', 'mediumtext', 'longtext'], + 'ALTER TABLE selfserve_wash_session_tasks MODIFY COLUMN description TEXT NULL AFTER task_text' + ); + + expect(implode("\n", $textDb->queries))->not->toContain('ALTER TABLE selfserve_wash_session_tasks MODIFY COLUMN description TEXT NULL AFTER task_text'); + } finally { + if ($hadDb) { + $GLOBALS['db'] = $previousDb; + } else { + unset($GLOBALS['db']); + } + } +}); + it('adds wash_started_at column for legacy selfserve wash session schemas', function (): void { $bootstrapContent = file_get_contents(app_path('classes/selfserve_schema_bootstrap.php'));