getMessage())->toContain('Failed to initialize cURL handle'); expect($e->getData())->toHaveKey('url'); expect($e->getData()['url'])->toBe('https://example.com'); } }); it('provides context for execution failures', function () { $exception = HandleException::executionFailed('Connection timeout', 28); expect($exception->getMessage())->toContain('cURL execution failed'); expect($exception->getData())->toHaveKey('curl_error_code'); expect($exception->getData())->toHaveKey('curl_error_message'); expect($exception->getData()['curl_error_code'])->toBe(28); expect($exception->getData()['curl_error_message'])->toBe('Connection timeout'); }); it('provides context for invalid output target', function () { $exception = HandleException::invalidOutputTarget('not-a-resource'); expect($exception->getMessage())->toContain('Invalid output target'); expect($exception->getData())->toHaveKey('output_type'); expect($exception->getData()['output_type'])->toBe('string'); }); it('provides context for setOption failures', function () { $exception = HandleException::setOptionFailed( HandleOption::Timeout, 'invalid', 'Invalid type' ); expect($exception->getMessage())->toContain('Failed to set cURL option'); expect($exception->getData())->toHaveKey('option'); expect($exception->getData())->toHaveKey('value'); expect($exception->getData())->toHaveKey('error'); expect($exception->getData()['option'])->toBe('Timeout'); }); it('provides context for invalid option types', function () { $exception = HandleException::invalidOptionType(HandleOption::Timeout, 'invalid'); expect($exception->getMessage())->toContain('Invalid type for option Timeout'); expect($exception->getMessage())->toContain('expected integer'); expect($exception->getMessage())->toContain('got string'); expect($exception->getData())->toHaveKey('option'); expect($exception->getData())->toHaveKey('expected_type'); expect($exception->getData())->toHaveKey('actual_type'); expect($exception->getData()['expected_type'])->toBe('integer'); expect($exception->getData()['actual_type'])->toBe('string'); }); it('provides context for pause failures', function () { $exception = HandleException::pauseFailed(Pause::Recv, 'Pause operation failed'); expect($exception->getMessage())->toContain('Failed to pause cURL handle'); expect($exception->getData())->toHaveKey('pause_flag'); expect($exception->getData())->toHaveKey('pause_value'); expect($exception->getData()['pause_flag'])->toBe('Recv'); }); it('provides context for escape failures', function () { $exception = HandleException::escapeFailed('some string'); expect($exception->getMessage())->toContain('Failed to URL-escape string'); expect($exception->getData())->toHaveKey('string_length'); expect($exception->getData()['string_length'])->toBe(strlen('some string')); }); it('provides context for unescape failures', function () { $exception = HandleException::unescapeFailed('some%20string'); expect($exception->getMessage())->toContain('Failed to URL-unescape string'); expect($exception->getData())->toHaveKey('string'); expect($exception->getData()['string'])->toBe('some%20string'); }); it('provides context for upkeep failures', function () { $exception = HandleException::upkeepFailed('Connection check failed'); expect($exception->getMessage())->toContain('cURL upkeep failed'); expect($exception->getData())->toHaveKey('error'); expect($exception->getData()['error'])->toBe('Connection check failed'); }); it('includes error details in real execution failure', function () { $handle = new Handle('https://invalid-domain-that-does-not-exist-99999.com'); $handle->setOption(HandleOption::Timeout, 3); try { $handle->fetch(); throw new Exception('Should have thrown HandleException'); } catch (HandleException $e) { expect($e->getMessage())->toContain('cURL execution failed'); expect($e->getData())->toHaveKey('curl_error_code'); expect($e->getData())->toHaveKey('curl_error_message'); expect($e->getData()['curl_error_code'])->toBeGreaterThan(0); expect($e->getData()['curl_error_message'])->not->toBeEmpty(); } }); it('includes error details in type validation failure', function () { $handle = new Handle(); try { $handle->setOption(HandleOption::Timeout, 'not-an-integer'); throw new Exception('Should have thrown HandleException'); } catch (HandleException $e) { expect($e->getMessage())->toContain('Invalid type'); expect($e->getData()['option'])->toBe('Timeout'); expect($e->getData()['expected_type'])->toBe('integer'); expect($e->getData()['actual_type'])->toBe('string'); } }); });