Added testing with SSL connection (#1624)

* Added testing with SSL connection

* Update CHANGELOG.md

* Revert changes

* Codestyle fixes

* Add version restriction

* Fixed issue with connecting to SSL port to check redis version

* Mark tests as relay-incompatible

* Codestyle fixes
This commit is contained in:
Vladyslav Vildanov
2026-01-20 17:45:10 +02:00
committed by GitHub
parent 0703c3cae6
commit 95e802430d
5 changed files with 251 additions and 6 deletions
+1
View File
@@ -71,6 +71,7 @@ services:
command: ${REDIS_EXTRA_ARGS:---enable-debug-command yes --enable-module-command yes --tls-auth-clients optional --save "" --requirepass "foobar"}
ports:
- "6372-6377:6372-6377"
- "27379-27384:27379-27384"
volumes:
- "./dockers/cluster:/redis/work"
profiles:
+3
View File
@@ -11,6 +11,9 @@
- Added retry support (#1616)
- Added support for VRANGE command (#1623)
### Maintenance
- Added testing with SSL connection (#1624)
## v3.3.0 (2025-11-24)
### Added
- Added cluster support for `XADD`, `XDEL` and `XRANGE` (#1587)
+9
View File
@@ -55,6 +55,15 @@
<env name="USE_RELAY" value="false" />
<env name="REDIS_STACK_SERVER_PORT" value="6479" />
<!-- SSL -->
<env name="STANDALONE_CA_CERT_PATH" value=".github/dockers/standalone/tls/ca.crt" />
<env name="REDIS_SSL_PORT" value="6666" />
<env name="CLUSTER_CA_CERT_PATH" value=".github/dockers/cluster/tls/ca.crt" />
<const
name="SSL_REDIS_CLUSTER_ENDPOINTS"
value="127.0.0.1:27379?password=foobar,127.0.0.1:27380?password=foobar,127.0.0.1:27381?password=foobar"
/>
<!-- Redis Cluster -->
<!-- Only master nodes endpoints included -->
<const
+112 -6
View File
@@ -180,12 +180,14 @@ abstract class PredisTestCase extends PHPUnit\Framework\TestCase
} elseif ($this->isUnprotectedTest()) {
$port = constant('REDIS_UNPROTECTED_SERVER_PORT');
$password = '';
} elseif ($this->isSSLTest()) {
$port = getenv('REDIS_SSL_PORT');
} else {
$port = constant('REDIS_SERVER_PORT');
}
return [
'scheme' => 'tcp',
'scheme' => $this->isSSLTest() ? 'tls' : 'tcp',
'host' => constant('REDIS_SERVER_HOST'),
'port' => $port,
'database' => constant('REDIS_SERVER_DBNUM'),
@@ -282,6 +284,28 @@ abstract class PredisTestCase extends PHPUnit\Framework\TestCase
],
$options
);
if ($this->isSSLTest()) {
$options = array_merge($options, [
'parameters' => [
'ssl' => [
'cafile' => getenv('CLUSTER_CA_CERT_PATH'),
'verify_peer' => true,
'verify_peer_name' => false,
],
],
]);
}
} else {
if ($this->isSSLTest()) {
$parameters = array_merge($parameters, [
'ssl' => [
'cafile' => getenv('STANDALONE_CA_CERT_PATH'),
'verify_peer' => true,
'verify_peer_name' => false,
],
]);
}
}
$client = new Client($parameters, $options);
@@ -294,6 +318,54 @@ abstract class PredisTestCase extends PHPUnit\Framework\TestCase
return $client;
}
/**
* Creates a client for version checking without SSL configuration.
* This is used to check Redis version before attempting SSL connection.
*
* @return Client
*/
protected function createClientForVersionCheck(): Client
{
// For SSL tests, temporarily override to use non-SSL configuration
$isSSL = $this->isSSLTest();
$isCluster = $this->isClusterTest();
if ($isSSL && $isCluster) {
// For cluster SSL tests, use non-SSL cluster endpoints
$endpoints = explode(',', constant('REDIS_CLUSTER_ENDPOINTS'));
$parameters = array_map(static function (string $elem) {
return 'tcp://' . $elem;
}, $endpoints);
} elseif ($isSSL) {
// For standalone SSL tests, use non-SSL port
$parameters = [
'scheme' => 'tcp',
'host' => constant('REDIS_SERVER_HOST'),
'port' => constant('REDIS_SERVER_PORT'),
'database' => constant('REDIS_SERVER_DBNUM'),
'password' => getenv('REDIS_PASSWORD') ?: constant('REDIS_PASSWORD'),
];
} else {
// For non-SSL tests, use default parameters
$parameters = $this->getDefaultParametersArray();
}
$commandsFactory = $this->getCommandFactory();
$options = array_merge(
['commands' => $commandsFactory],
getenv('USE_RELAY') ? ['connections' => 'relay'] : []
);
if ($isCluster) {
$options['cluster'] = 'redis';
}
$client = new Client($parameters, $options);
$client->connect();
return $client;
}
/**
* Returns a basic mock object of a connection to a single Redis node.
*
@@ -371,7 +443,9 @@ abstract class PredisTestCase extends PHPUnit\Framework\TestCase
if (isset($this->info)) {
$info = $this->info;
} else {
$client = $this->createClient(null, null, true);
// For SSL tests, connect to non-SSL port to check version first
// This prevents connection failures on Redis < 7.2.0 which doesn't support SSL
$client = $this->createClientForVersionCheck();
$info = array_change_key_case($client->info());
$this->info = $info;
}
@@ -383,7 +457,7 @@ abstract class PredisTestCase extends PHPUnit\Framework\TestCase
// Redis < 2.6
$version = $info['redis_version'];
} else {
$client = $this->createClient(null, null, true);
$client = $this->createClientForVersionCheck();
$connection = $client->getConnection();
throw new RuntimeException("Unable to retrieve a valid server info payload from $connection");
}
@@ -607,6 +681,34 @@ abstract class PredisTestCase extends PHPUnit\Framework\TestCase
&& in_array('cluster', $annotations['method']['group'], true);
}
/**
* Check annotations if it's matches to SSL test scenario.
*
* @return bool
*/
protected function isSSLTest(): bool
{
$annotations = TestUtil::parseTestMethodAnnotations(
get_class($this),
$this->getName(false)
);
$annotationExists = isset($annotations['method']['requiresRedisVersion']);
if (!$annotationExists) {
foreach ($this->modulesMapping as $module => $configuration) {
if (isset($annotations['method'][$configuration['annotation']])) {
$annotationExists = true;
}
}
}
return $annotationExists
&& isset($annotations['method']['group'])
&& in_array('connected', $annotations['method']['group'], true)
&& in_array('ssl', $annotations['method']['group'], true);
}
/**
* Check annotations if it's matches to stack test scenario.
*
@@ -646,10 +748,14 @@ abstract class PredisTestCase extends PHPUnit\Framework\TestCase
*/
protected function prepareClusterEndpoints(): array
{
$endpoints = explode(',', constant('REDIS_CLUSTER_ENDPOINTS'));
$endpoints = explode(
',',
constant($this->isSSLTest() ? 'SSL_REDIS_CLUSTER_ENDPOINTS' : 'REDIS_CLUSTER_ENDPOINTS')
);
$scheme = $this->isSSLTest() ? 'tls' : 'tcp';
return array_map(static function (string $elem) {
return 'tcp://' . $elem;
return array_map(static function (string $elem) use ($scheme) {
return "{$scheme}://" . $elem;
}, $endpoints);
}
}
+126
View File
@@ -0,0 +1,126 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2025 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis;
use Predis\Connection\Resource\Exception\StreamInitException;
use PredisTestCase;
class SSLTest extends PredisTestCase
{
/**
* @group connected
* @group ssl
* @group relay-incompatible
* @requiresRedisVersion >= 7.2.0
* @return void
*/
public function testExecuteCommandOverSSLConnection()
{
$redis = $this->createClient();
$this->assertEquals('PONG', $redis->ping());
}
/**
* @group connected
* @group ssl
* @group relay-incompatible
* @requiresRedisVersion >= 7.2.0
* @return void
*/
public function testExecuteCommandOverSSLConnectionFailsOnIncorrectCertificate()
{
$redis = new Client($this->getDefaultParametersArray() + [
'ssl' => ['cafile' => '/tmp/invalid.crt', 'verify_peer' => true, 'verify_peer_name' => false]]
);
$this->expectException(StreamInitException::class);
$this->expectExceptionMessage('Error while switching to encrypted communication');
$redis->ping();
}
/**
* @group connected
* @group ssl
* @group relay-incompatible
* @requiresRedisVersion >= 7.2.0
* @return void
*/
public function testExecuteCommandOverSSLConnectionWithoutSSLConfig()
{
$redis = new Client($this->getDefaultParametersArray());
$this->expectException(StreamInitException::class);
$this->expectExceptionMessage('Error while switching to encrypted communication');
$redis->ping();
}
/**
* @group connected
* @group ssl
* @group cluster
* @group relay-incompatible
* @requiresRedisVersion >= 7.2.0
* @return void
*/
public function testClusterExecuteCommandOverSSLConnection()
{
$redis = $this->createClient();
$redis->set('foo', 'bar');
$this->assertEquals('bar', $redis->get('foo'));
}
/**
* @group connected
* @group ssl
* @group cluster
* @group relay-incompatible
* @requiresRedisVersion >= 7.2.0
* @return void
*/
public function testClusterExecuteCommandOverSSLConnectionFailsOnIncorrectCertificate()
{
$redis = new Client($this->getDefaultParametersArray(), [
'cluster' => 'redis',
'parameters' => [
'ssl' => ['cafile' => '/tmp/invalid.crt', 'verify_peer' => true, 'verify_peer_name' => false],
],
]);
$this->expectException(StreamInitException::class);
$this->expectExceptionMessage('Error while switching to encrypted communication');
$redis->set('foo', 'bar');
}
/**
* @group connected
* @group ssl
* @group cluster
* @group relay-incompatible
* @requiresRedisVersion >= 7.2.0
* @return void
*/
public function testClusterExecuteCommandOverSSLConnectionWithoutSSLConfig()
{
$redis = new Client($this->getDefaultParametersArray(), [
'cluster' => 'redis',
]);
$this->expectException(StreamInitException::class);
$this->expectExceptionMessage('Error while switching to encrypted communication');
$redis->set('foo', 'bar');
}
}