Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .github/workflows/split-testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,23 @@ jobs:
${run_tests_cmd}"; then
overall_exit_code=1
fi

# Run SQLite pass (skip for PdoEventSourcing - SQLite event store not supported)
if [[ "$dir" != *"PdoEventSourcing"* ]]; then
local sqlite_db="/tmp/ecotone_${slug}_test.db"
local sqlite_db_secondary="/tmp/ecotone_${slug}_test_b.db"
# Use 4 slashes for absolute paths: sqlite:// + / + /path = sqlite:////path
# This is needed because both enqueue/dsn and Doctrine's DsnParser
# require 4 slashes for absolute paths to be parsed correctly
if ! _run_tests "$dir (SQLite)" "\
rm -f '${sqlite_db}' '${sqlite_db_secondary}' && \
cd '${dir}' && \
DATABASE_DSN='sqlite:///${sqlite_db}' \
SECONDARY_DATABASE_DSN='sqlite:///${sqlite_db_secondary}' \
${run_tests_cmd}"; then
overall_exit_code=1
fi
fi
else
# No DB dependency: run tests with no DB env vars
if ! _run_tests "$dir" "cd '${dir}' && ${run_tests_cmd}"; then
Expand Down
20 changes: 19 additions & 1 deletion .github/workflows/test-monorepo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -145,4 +145,22 @@ jobs:
run: vendor/bin/behat -vvv
env:
DATABASE_DSN: mysql://ecotone:[email protected]:3306/ecotone?serverVersion=8.0
SECONDARY_DATABASE_DSN: pgsql://ecotone:[email protected]:5432/ecotone?serverVersion=16
SECONDARY_DATABASE_DSN: pgsql://ecotone:[email protected]:5432/ecotone?serverVersion=16

- name: Test PHPUnit on SQLite
run: |
rm -f /tmp/ecotone_test.db /tmp/ecotone_test_b.db
vendor/bin/phpunit --no-coverage --exclude-testsuite 'Event Sourcing tests'
env:
# Use 4 slashes for absolute paths: sqlite:// + / + /path = sqlite:////path
DATABASE_DSN: sqlite:////tmp/ecotone_test.db
SECONDARY_DATABASE_DSN: sqlite:////tmp/ecotone_test_b.db

- name: Test Behat on SQLite
run: |
rm -f /tmp/ecotone_test.db /tmp/ecotone_test_b.db
vendor/bin/behat -vvv
env:
# Use 4 slashes for absolute paths: sqlite:// + / + /path = sqlite:////path
DATABASE_DSN: sqlite:////tmp/ecotone_test.db
SECONDARY_DATABASE_DSN: sqlite:////tmp/ecotone_test_b.db
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,6 @@
"enqueue/amqp-lib": "^0.10.25",
"enqueue/redis": "^0.10.9",
"enqueue/sqs": "^0.10.15",
"enqueue/dsn": "^0.10.4",
"enqueue/enqueue": "^0.10.0",
"ext-amqp": "*",
"laminas/laminas-code": "^4",
Expand Down Expand Up @@ -165,7 +164,8 @@
"timacdonald/log-fake": "^2.0",
"symfony/monolog-bundle": "^3.10",
"kwn/php-rdkafka-stubs": "^2.2",
"symfony/var-exporter": "^6.4|^7.0|^8.0"
"symfony/var-exporter": "^6.4|^7.0|^8.0",
"enqueue/dsn": "^0.10.27"
},
"conflict": {
"symfony/doctrine-messenger": ">7.0.5 < 7.1.0",
Expand Down
14 changes: 2 additions & 12 deletions packages/Dbal/src/DbaBusinessMethod/DbalBusinessMethodHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -297,24 +297,14 @@ private function getArrayStringType()
return $this->getArrayStringTypeValue();
}

/**
* Get the integer value for ArrayParameterType::INTEGER
*/
private function getArrayIntegerTypeValue(): int
{
// DBAL 3.x uses Connection::ARRAY_PARAM_OFFSET (100) + ParameterType::INTEGER (1) = 101
// But the actual implementation uses 102 for INTEGER arrays
return 102;
return 101;
}

/**
* Get the integer value for ArrayParameterType::STRING
*/
private function getArrayStringTypeValue(): int
{
// DBAL 3.x uses Connection::ARRAY_PARAM_OFFSET (100) + ParameterType::STRING (2) = 102
// But the actual implementation uses 101 for STRING arrays
return 101;
return 102;
}

/**
Expand Down
53 changes: 51 additions & 2 deletions packages/Dbal/src/EnqueueDbal/DbalConnectionFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,11 @@ private function parseDsn(string $dsn, ?array $config = null): array
}

$doctrineScheme = $supported[$parsedDsn->getScheme()];

if ($doctrineScheme === 'pdo_sqlite') {
return $this->buildSqliteConfig($parsedDsn, $dsn, $config);
}

$dsnHasProtocolOnly = $parsedDsn->getScheme().':' === $dsn;
if ($dsnHasProtocolOnly && is_array($config) && array_key_exists('connection', $config)) {
$default = [
Expand All @@ -178,8 +183,6 @@ private function parseDsn(string $dsn, ?array $config = null): array
'lazy' => true,
'connection' => [
'driver' => $doctrineScheme,
// Don't use the URL directly, as it might cause issues
// 'url' => $url,
'host' => $parsedDsn->getHost() ?: 'localhost',
'port' => $parsedDsn->getPort() ?: ($doctrineScheme === 'pdo_pgsql' ? 5432 : 3306),
'user' => $parsedDsn->getUser() ?: 'root',
Expand All @@ -188,4 +191,50 @@ private function parseDsn(string $dsn, ?array $config = null): array
],
];
}

private function buildSqliteConfig(Dsn $parsedDsn, string $originalDsn, ?array $config = null): array
{
$path = $parsedDsn->getPath();

if ($path === null || $path === '') {
$path = $this->extractSqlitePathFromDsn($originalDsn);
}

if ($path !== ':memory:') {
$path = ltrim($path, '/');
if (! str_starts_with($path, '/')) {
$path = '/' . $path;
}
}

$connectionConfig = [
'driver' => 'pdo_sqlite',
'path' => $path,
];

if (is_array($config) && array_key_exists('connection', $config)) {
$connectionConfig = array_replace_recursive($connectionConfig, $config['connection']);
}

return [
'lazy' => true,
'connection' => $connectionConfig,
];
}

private function extractSqlitePathFromDsn(string $dsn): string
{
if (preg_match('#^sqlite3?:///?(.*)$#', $dsn, $matches)) {
$pathPart = $matches[1];
if ($pathPart === '' || $pathPart === ':memory:') {
return ':memory:';
}
if (str_starts_with($pathPart, '/')) {
return $pathPart;
}
return '/' . $pathPart;
}

return ':memory:';
}
}
19 changes: 16 additions & 3 deletions packages/Dbal/tests/DbalMessagingTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ protected function getConnection(): Connection
return $this->getConnectionFactory()->createContext()->getDbalConnection();
}

protected function isUsingSqlite(): bool
{
return $this->getConnection()->getDatabasePlatform() instanceof \Doctrine\DBAL\Platforms\SqlitePlatform;
}

public static function cleanUpDbalTables(Connection $connection): void
{
self::deleteTable('enqueue', $connection);
Expand Down Expand Up @@ -140,9 +145,17 @@ protected function connectionForTenantB(): ConnectionFactory
return $this->tenantBConnection;
}

$connectionFactory = DbalConnection::fromDsn(
getenv('SECONDARY_DATABASE_DSN') ? getenv('SECONDARY_DATABASE_DSN') : 'mysql://ecotone:secret@localhost:3306/ecotone'
);
$secondaryDsn = getenv('SECONDARY_DATABASE_DSN');
if (! $secondaryDsn) {
$primaryDsn = getenv('DATABASE_DSN') ?: '';
if (str_starts_with($primaryDsn, 'sqlite')) {
$secondaryDsn = 'sqlite:///tmp/ecotone_tenant_b.db';
} else {
$secondaryDsn = 'mysql://ecotone:secret@localhost:3306/ecotone';
}
}

$connectionFactory = DbalConnection::fromDsn($secondaryDsn);

$this->tenantBConnection = $connectionFactory;
return $connectionFactory;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
*/
interface PersonService
{
#[DbalWrite('INSERT INTO persons VALUES (:personId, :name, DEFAULT)')]
#[DbalWrite('INSERT INTO persons (person_id, name) VALUES (:personId, :name)')]
public function insert(int $personId, string $name): void;

#[DbalWrite('INSERT INTO persons VALUES (:personId, :name, DEFAULT)')]
#[DbalWrite('INSERT INTO persons (person_id, name) VALUES (:personId, :name)')]
public function insertWithParameterName(
#[DbalParameter(name: 'personId')] int $id,
string $name
Expand All @@ -43,23 +43,23 @@ public function changeRolesWithValueObjects(
#[DbalParameter(convertToMediaType: MediaType::APPLICATION_JSON)] array $roles
): void;

#[DbalWrite('INSERT INTO persons VALUES (:personId, :name, DEFAULT)')]
#[DbalWrite('INSERT INTO persons (person_id, name) VALUES (:personId, :name)')]
public function insertWithExpression(
int $personId,
#[DbalParameter(expression: 'payload.toLowerCase()')] PersonName $name
): void;

#[DbalWrite('INSERT INTO persons VALUES (:personId, :name, DEFAULT)')]
#[DbalWrite('INSERT INTO persons (person_id, name) VALUES (:personId, :name)')]
public function insertWithServiceExpression(
int $personId,
#[DbalParameter(expression: "reference('converter').normalize(payload)")] PersonName $name
): void;

#[DbalWrite('INSERT INTO persons VALUES (:personId, :name, :roles)')]
#[DbalWrite('INSERT INTO persons (person_id, name, roles) VALUES (:personId, :name, :roles)')]
#[DbalParameter(name: 'roles', expression: "['ROLE_ADMIN']", convertToMediaType: MediaType::APPLICATION_JSON)]
public function registerAdmin(int $personId, string $name): void;

#[DbalWrite('INSERT INTO persons VALUES (:personId, :name, :roles)')]
#[DbalWrite('INSERT INTO persons (person_id, name, roles) VALUES (:personId, :name, :roles)')]
#[DbalParameter(name: 'roles', expression: "name === 'Johny' ? ['ROLE_ADMIN'] : []", convertToMediaType: MediaType::APPLICATION_JSON)]
public function registerUsingMethodParameters(int $personId, string $name): void;
}
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,10 @@ classesToResolve: [get_class($converter)],

public function test_adding_non_json_document_should_fail()
{
if ($this->isUsingSqlite()) {
$this->markTestSkipped('SQLite does not validate JSON at the database level');
}

$ecotone = $this->bootstrapEcotone();
$documentStore = $ecotone->getGateway(DocumentStore::class);

Expand Down
17 changes: 12 additions & 5 deletions packages/Dbal/tests/Integration/ORMTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,13 @@ public static function multiTenantConnectionConfiguration(): iterable
*/
public function test_flushing_object_manager_on_command_bus_with_tenant(array $services, array $namespaces, bool $enableDoctrineORMAggregates, MultiTenantConfiguration $multiTenantConfiguration): void
{
$this->setupUserTable($this->connectionForTenantA()->getConnection());
$this->setupUserTable($this->connectionForTenantB()->getConnection());
/** @var EcotoneManagerRegistryConnectionFactory $tenantAConnection */
$tenantAConnection = $services['tenant_a_connection'];
/** @var EcotoneManagerRegistryConnectionFactory $tenantBConnection */
$tenantBConnection = $services['tenant_b_connection'];

$this->setupUserTable($tenantAConnection->getConnection());
$this->setupUserTable($tenantBConnection->getConnection());

$ecotone = EcotoneLite::bootstrapFlowTesting(
containerOrAvailableServices: array_merge([
Expand Down Expand Up @@ -209,7 +214,9 @@ public function test_flushing_object_manager_on_command_bus_with_tenant(array $s
*/
public function test_flushing_object_manager_on_command_bus_without_multi_tenancy(array $services, array $namespaces, bool $enableDoctrineORMAggregates): void
{
$this->setupUserTable();
/** @var EcotoneManagerRegistryConnectionFactory $connectionFactory */
$connectionFactory = $services[DbalConnectionFactory::class];
$this->setupUserTable($connectionFactory->getConnection());

$ecotone = EcotoneLite::bootstrapFlowTesting(
containerOrAvailableServices: array_merge([
Expand Down Expand Up @@ -253,9 +260,9 @@ public function test_flushing_object_manager_on_command_bus_without_multi_tenanc
*/
public function test_disabling_flushing_object_manager_on_command_bus(array $services, array $namespaces, bool $enableDoctrineORMAggregates)
{
$this->setupUserTable();
/** @var EcotoneManagerRegistryConnectionFactory $connectionFactory */
$connectionFactory = $services[DbalConnectionFactory::class];
$this->setupUserTable($connectionFactory->getConnection());
$entityManager = $connectionFactory->getRegistry()->getManager();

$ecotone = EcotoneLite::bootstrapFlowTesting(
Expand Down Expand Up @@ -286,9 +293,9 @@ public function test_disabling_flushing_object_manager_on_command_bus(array $ser
*/
public function test_object_manager_reconnects_on_command_bus(array $services, array $namespaces, bool $enableDoctrineORMAggregates)
{
$this->setupUserTable();
/** @var EcotoneManagerRegistryConnectionFactory $connectionFactory */
$connectionFactory = $services[DbalConnectionFactory::class];
$this->setupUserTable($connectionFactory->getConnection());
$entityManager = $connectionFactory->getRegistry()->getManager();

$ecotone = EcotoneLite::bootstrapFlowTesting(
Expand Down
8 changes: 8 additions & 0 deletions packages/Dbal/tests/Integration/ReconnectTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ final class ReconnectTest extends DbalMessagingTestCase
{
public function test_it_will_automatically_reconnect(): void
{
if ($this->isUsingSqlite()) {
$this->markTestSkipped('SQLite uses file-based connections that do not support reconnection testing');
}

$connectionFactory = $this->connectionForTenantA();

// Create the necessary database tables
Expand All @@ -49,6 +53,10 @@ public function test_it_will_automatically_reconnect(): void

public function test_it_will_automatically_reconnect_for_multi_tenant_connection(): void
{
if ($this->isUsingSqlite()) {
$this->markTestSkipped('SQLite uses file-based connections that do not support reconnection testing');
}

$connectionFactory = $this->connectionForTenantA();

// Create the necessary database tables
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ public function test_turning_on_transactions_for_polling_consumer()

public function test_reconnecting_on_lost_connection_during_commit()
{
if ($this->isUsingSqlite()) {
$this->markTestSkipped('SQLite does not support connection breaking/recovering testing');
}

// Now create the actual test instance with the connection breaking module
$ecotoneLite = EcotoneLite::bootstrapFlowTesting(
[Person::class, MultipleInternalCommandsService::class, ConnectionBreakingModule::class],
Expand Down Expand Up @@ -131,6 +135,10 @@ public function test_reconnecting_on_lost_connection_during_commit()
*/
public function test_reconnecting_on_lost_connection_during_dead_letter_storage()
{
if ($this->isUsingSqlite()) {
$this->markTestSkipped('SQLite does not support connection breaking/recovering testing');
}

// First, create a regular EcotoneLite instance to set up the database tables
$setupEcotoneLite = EcotoneLite::bootstrapFlowTesting(
[Person::class, MultipleInternalCommandsService::class],
Expand Down
Loading
Loading