1700000000
1700003600
300
2
2
17000000001234.567890.12
17000003002345.678901.23
XML;
$result = rrdxport2array($xml);
expect($result)->toBeArray()
->and($result['meta'])->toBeArray()
->and($result['meta']['start'])->toBe('1700000000')
->and($result['meta']['end'])->toBe('1700003600')
->and($result['meta']['step'])->toBe('300')
->and($result['meta']['rows'])->toBe('2')
->and($result['meta']['columns'])->toBe('2')
->and($result['meta']['legend'])->toBeArray()
->and($result['meta']['legend']['col1'])->toBe('traffic_in')
->and($result['meta']['legend']['col2'])->toBe('traffic_out')
->and($result['data'])->toBeArray()
->and($result['data'])->toHaveCount(2)
->and($result['data'][1]['timestamp'])->toBe('1700000000')
->and($result['data'][1]['col1'])->toBe('1234.56');
});
// --- rrdxport2array: rrdtool error XML has no meta key ---
test('rrdxport2array returns no meta key for rrdtool error XML', function () {
$xml = 'ERROR: opening rrd: No such file';
$result = rrdxport2array($xml);
expect($result)->toBeArray()
->and(isset($result['meta']))->toBeFalse();
});
// --- rrdxport2array: empty string input returns array without meta ---
test('rrdxport2array returns array without meta for empty input', function () {
$result = rrdxport2array('');
expect($result)->toBeArray()
->and(isset($result['meta']))->toBeFalse();
});
// --- rrdxport2array: xport with zero data rows has meta but empty data ---
test('rrdxport2array handles xport with no data rows', function () {
$xml = <<<'XML'
1700000000
1700003600
300
0
1
XML;
$result = rrdxport2array($xml);
expect($result)->toBeArray()
->and(isset($result['meta']))->toBeTrue()
->and($result['meta']['start'])->toBe('1700000000')
->and(empty($result['data']))->toBeTrue();
});
// --- rrdxport2array: strips non-XML preamble lines (rrdtool 1.2.30 bug) ---
test('rrdxport2array strips non-XML preamble from rrdtool output', function () {
$xml = "OK u:0.00 s:0.00 r:0.01\n" .
'' . "\n" .
"\n\n1700000000\n1700003600\n" .
"300\n0\n1\n" .
"\n\n\n\n";
$result = rrdxport2array($xml);
expect(isset($result['meta']))->toBeTrue()
->and($result['meta']['start'])->toBe('1700000000');
});
// --- Export guard: mirrors the early-exit logic in graph_xport.php ---
function xportShouldFail(mixed $xport_array): bool {
return !is_array($xport_array) || !isset($xport_array['meta']['start']);
}
test('export guard rejects false return from rrdtool_function_xport', function () {
expect(xportShouldFail(false))->toBeTrue();
});
test('export guard rejects empty array', function () {
expect(xportShouldFail([]))->toBeTrue();
});
test('export guard rejects array with error key but no meta', function () {
expect(xportShouldFail(['error' => 'some rrdtool error']))->toBeTrue();
});
test('export guard rejects meta without start key', function () {
expect(xportShouldFail(['meta' => ['columns' => 1]]))->toBeTrue();
});
test('export guard passes valid xport structure', function () {
$valid = [
'meta' => [
'start' => '1700000000',
'end' => '1700003600',
'step' => '300',
'rows' => '2',
'columns' => '1',
],
'data' => [],
];
expect(xportShouldFail($valid))->toBeFalse();
});
// --- rrd.php guard: mirrors the !isset($xport_array['meta']) check ---
function xportMetaMissing(array $xport_array): bool {
return !isset($xport_array['meta']);
}
test('rrd guard detects missing meta from error XML parse', function () {
$errorResult = rrdxport2array(
'ERROR: ds not found'
);
expect(xportMetaMissing($errorResult))->toBeTrue();
});
test('rrd guard passes when meta exists', function () {
$xml = <<<'XML'
1700000000
1700003600
300
0
1
XML;
$result = rrdxport2array($xml);
expect(xportMetaMissing($result))->toBeFalse();
});