|
| 1 | +<?php |
| 2 | +/* |
| 3 | + +-------------------------------------------------------------------------+ |
| 4 | + | Copyright (C) 2004-2026 The Cacti Group | |
| 5 | + +-------------------------------------------------------------------------+ |
| 6 | + | Cacti: The Complete RRDtool-based Graphing Solution | |
| 7 | + +-------------------------------------------------------------------------+ |
| 8 | +*/ |
| 9 | + |
| 10 | +/* |
| 11 | + * Mutation protection for the data_input_data column-expansion fix on |
| 12 | + * develop. Two write paths (change_data_template in lib/template.php and |
| 13 | + * api_data_source_duplicate in lib/api_data_source.php) had to grow from |
| 14 | + * the four-column 1.2.x shape to the seven-column develop shape. A |
| 15 | + * single-character mutation against either site is detectable through |
| 16 | + * the source-level guards below. |
| 17 | + */ |
| 18 | + |
| 19 | +$templateSource = file_get_contents(__DIR__ . '/../../lib/template.php'); |
| 20 | +$apiDataSourceSource = file_get_contents(__DIR__ . '/../../lib/api_data_source.php'); |
| 21 | + |
| 22 | +if (!function_exists('_mut_extract_function')) { |
| 23 | + function _mut_extract_function(string $source, string $name): string { |
| 24 | + $pattern = '/^function\s+' . preg_quote($name, '/') . '\b[^{]*\{.*?^\}/sm'; |
| 25 | + return preg_match($pattern, $source, $m) ? $m[0] : ''; |
| 26 | + } |
| 27 | +} |
| 28 | + |
| 29 | +test('change_data_template REPLACE keeps seven placeholders (Mutation Protection)', function () use ($templateSource) { |
| 30 | + /* If a mutation narrows the placeholder list back to four `?`, the |
| 31 | + * seven-element bind array no longer matches and db_execute_prepared() |
| 32 | + * silently fails on every template-to-child copy. */ |
| 33 | + $body = _mut_extract_function($templateSource, 'change_data_template'); |
| 34 | + $slice = substr($body, strpos($body, 'REPLACE INTO data_input_data'), 800); |
| 35 | + expect(substr_count($slice, '?'))->toBe(7); |
| 36 | +}); |
| 37 | + |
| 38 | +test('api_data_source_duplicate INSERT keeps seven placeholders (Mutation Protection)', function () use ($apiDataSourceSource) { |
| 39 | + $body = _mut_extract_function($apiDataSourceSource, 'api_data_source_duplicate'); |
| 40 | + $slice = substr($body, strpos($body, 'INSERT IGNORE INTO data_input_data'), 800); |
| 41 | + expect(substr_count($slice, '?'))->toBe(7); |
| 42 | +}); |
| 43 | + |
| 44 | +test('change_data_template binds function-scope identity, not parent template row (Mutation Protection)', function () use ($templateSource) { |
| 45 | + /* The whole point of the fix is to stamp the child row with the new |
| 46 | + * data source's identity tuple. If a mutation reverts the bind list |
| 47 | + * to the parent's `$item['local_data_id']` / `$item['host_id']` |
| 48 | + * (always 0 for templates), the child rows go out orphaned again. */ |
| 49 | + $body = _mut_extract_function($templateSource, 'change_data_template'); |
| 50 | + $slice = substr($body, strpos($body, 'REPLACE INTO data_input_data'), 800); |
| 51 | + |
| 52 | + expect($slice)->toContain('$data_template_id,'); |
| 53 | + expect($slice)->toContain('$local_data_id,'); |
| 54 | + expect($slice)->toContain('$host_id,'); |
| 55 | + expect(strpos($slice, "\$item['local_data_id']"))->toBeFalse(); |
| 56 | + expect(strpos($slice, "\$item['host_id']"))->toBeFalse(); |
| 57 | +}); |
| 58 | + |
| 59 | +test('change_data_template looks up host_id from data_local for the new row (Mutation Protection)', function () use ($templateSource) { |
| 60 | + /* If a mutation drops the SELECT host_id, we lose the ability to |
| 61 | + * stamp the child row with the device the data source actually |
| 62 | + * belongs to. */ |
| 63 | + $body = _mut_extract_function($templateSource, 'change_data_template'); |
| 64 | + expect($body)->toContain('SELECT host_id'); |
| 65 | + expect($body)->toContain('FROM data_local'); |
| 66 | + expect($body)->toContain('[$local_data_id]'); |
| 67 | +}); |
| 68 | + |
| 69 | +test('seven-column INSERT/REPLACE shape is consistent across both write paths (Mutation Protection)', function () use ($templateSource, $apiDataSourceSource) { |
| 70 | + /* The two write paths must agree on the column list. A mutation in |
| 71 | + * one path only would create a schema-shape skew that downstream |
| 72 | + * consumers (update_poller_cache) can't reconcile. */ |
| 73 | + $expectedColumns = '(data_input_field_id, data_template_data_id, data_template_id, local_data_id, host_id, t_value, value)'; |
| 74 | + expect($templateSource)->toContain($expectedColumns); |
| 75 | + expect($apiDataSourceSource)->toContain($expectedColumns); |
| 76 | +}); |
0 commit comments