toBe(204) ->and(remote_agent_http_status(['Content-Type: text/plain']))->toBeNull() ->and(remote_agent_http_status(['HTTP/1.1 invalid']))->toBeNull(); }); test('HTTPS remote-agent context verifies peers and disables redirects by default', function () { $options = remote_agent_context_options('https', 10, true, '/tmp/test-ca.pem', 'collector.example.test'); expect($options['ssl']['verify_peer'])->toBeTrue() ->and($options['ssl']['verify_peer_name'])->toBeTrue() ->and($options['ssl']['allow_self_signed'])->toBeFalse() ->and($options['ssl']['peer_name'])->toBe('collector.example.test') ->and($options['ssl']['cafile'])->toBe('/tmp/test-ca.pem') ->and($options['http']['timeout'])->toBe(10) ->and($options['http']['follow_location'])->toBe(0) ->and($options['http']['max_redirects'])->toBe(0) ->and($options)->not->toHaveKey('https'); }); test('remote-agent TLS verification requires an explicit opt out', function () { $options = remote_agent_context_options('https', 5, false, '', '127.0.0.1'); expect($options['ssl']['verify_peer'])->toBeFalse() ->and($options['ssl']['verify_peer_name'])->toBeFalse() ->and($options['ssl']['allow_self_signed'])->toBeTrue() ->and($options['http']['timeout'])->toBe(5) ->and($options['ssl'])->not->toHaveKey('cafile'); }); test('HTTP remote-agent context applies timeout without TLS options', function () { $options = remote_agent_context_options('http', 5, true); expect($options)->toHaveKey('http') ->and($options)->not->toHaveKey('ssl'); }); test('remote collector handoff enforces path status and response-size contracts', function () { $source = file_get_contents(dirname(__DIR__, 4) . '/lib/functions.php'); expect($source)->toContain('strpbrk($url, "\\0\\r\\n")') ->and($source)->toContain('REMOTE_AGENT_MAX_RESPONSE_BYTES + 1') ->and($source)->toContain('$http_response_header = [];') ->and($source)->toContain('remote_agent_http_status($http_response_header)') ->and($source)->toContain('$status < 200 || $status >= 300'); }); test('remote graph JSON accepts only strict supported image envelopes', function (string $image) { $envelope = ['image' => base64_encode($image), 'graph_start' => 1]; expect(remote_graph_json_envelope(json_encode($envelope, JSON_THROW_ON_ERROR)))->toBe($envelope); })->with([ 'png' => "\x89PNG\r\n\x1a\ndata", 'gif' => 'GIF89adata', 'svg' => '', 'svg with declaration' => '' . PHP_EOL . '' ]); test('remote graph JSON rejects malformed missing and unsupported images', function (string $response) { expect(remote_graph_json_envelope($response))->toBeFalse(); })->with([ 'malformed JSON' => '{', 'non-object JSON' => '[]', 'missing image' => '{"graph_start":1}', 'non-string image' => '{"image":7}', 'invalid base64' => '{"image":"%%%"}', 'empty image' => '{"image":""}', 'unsupported data' => '{"image":"' . base64_encode('plain text') . '"}', 'non-SVG XML' => '{"image":"' . base64_encode('') . '"}' ]); test('remote graph endpoints preserve local request identity over remote metadata', function () { $source = file_get_contents(dirname(__DIR__, 4) . '/graph_json.php'); expect($source)->toContain("unset(\$decoded['type'], \$decoded['local_graph_id'], \$decoded['rra_id'])") ->and($source)->toContain('array_merge($decoded, $oarray)'); });