Skip to content
Merged
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
2 changes: 1 addition & 1 deletion Config/module.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<language>en_US</language>
<language>fr_FR</language>
</languages>
<version>3.0.2</version>
<version>3.0.3</version>
<authors>
<author>
<name>Vincent Lopes-Vicente</name>
Expand Down
25 changes: 20 additions & 5 deletions ShortCode.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
namespace ShortCode;

use Propel\Runtime\Connection\ConnectionInterface;
use Propel\Runtime\Propel;
use Propel\Runtime\Exception\PropelException;
use ShortCode\Model\ShortCodeQuery;
use Thelia\Core\Install\Database;
Expand All @@ -23,14 +24,20 @@ class ShortCode extends BaseModule
/** @var string */
const DOMAIN_NAME = 'shortcode';

/**
* Create the tables on a fresh install.
*
* The install script starts with DROP TABLE, so it only runs when the tables are missing.
* The database is asked rather than an "is_initialized" flag in the module configuration:
* a shop whose tables were created another way, or whose configuration was lost, would
* otherwise have every short code wiped by a mere reactivation.
*/
public function preActivation(?ConnectionInterface $con = null): bool
{
if (!$this->getConfigValue('is_initialized', false)) {
$database = new Database($con);
$con ??= Propel::getWriteConnection('TheliaMain');

$database->insertSql(null, array(__DIR__ . '/Config/TheliaMain.sql'));

$this->setConfigValue('is_initialized', true);
if (!$this->hasTable($con, 'short_code')) {
(new Database($con))->insertSql(null, [__DIR__.'/Config/TheliaMain.sql']);
}

return true;
Expand Down Expand Up @@ -86,4 +93,12 @@ public static function deactivateShortCode($shortCodeName)
->save();
}
}

private function hasTable(ConnectionInterface $con, string $table): bool
{
$statement = $con->prepare('SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = :table');
$statement->execute(['table' => $table]);

return (int) $statement->fetchColumn() > 0;
}
}
127 changes: 127 additions & 0 deletions Tests/Integration/ActivationTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<?php

declare(strict_types=1);

/*
* This file is part of the Thelia package.
* http://www.thelia.net
*
* (c) OpenStudio <info@thelia.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace ShortCode\Tests\Integration;

use ShortCode\ShortCode;
use PHPUnit\Framework\Attributes\Test;
use Propel\Runtime\Connection\ConnectionInterface;
use Propel\Runtime\Propel;
use Thelia\Core\Install\Database;
use Thelia\Model\ModuleConfigQuery;
use Thelia\Model\ModuleQuery;
use Thelia\Test\IntegrationTestCase;

/**
* Activating the module creates its tables on a fresh install, and never touches the
* short codes of a shop that already has them.
*
* The install script starts with DROP TABLE, which commits implicitly: no test transaction,
* the tables are restored and the test row removed by hand.
*/
final class ActivationTest extends IntegrationTestCase
{
private const INSTALL_SCRIPT = __DIR__.'/../../Config/TheliaMain.sql';

protected bool $useTransaction = false;

private ConnectionInterface $con;

private ?int $rowId = null;

protected function setUp(): void
{
parent::setUp();

$this->con = Propel::getWriteConnection('TheliaMain');
}

protected function tearDown(): void
{
if (!$this->hasTable('short_code')) {
(new Database($this->con))->insertSql(null, [self::INSTALL_SCRIPT]);
}

if (null !== $this->rowId) {
$this->con->prepare('DELETE FROM `short_code` WHERE `id` = :id')->execute(['id' => $this->rowId]);
}

parent::tearDown();
}

#[Test]
public function reactivatingTheModuleKeepsTheExistingRowsEvenWithoutItsInstallFlag(): void
{
$this->con->exec("INSERT INTO `short_code` (`tag`, `event`, `active`) VALUES ('activation_test', 'activation_test', 1)");
$this->rowId = (int) $this->con->lastInsertId();

// A shop whose tables were created without going through the activation: by a schema
// command, or with its module configuration lost.
$this->forgetInstallFlag();

$this->activate();

self::assertSame(1, (int) $this->con->query('SELECT COUNT(*) FROM `short_code` WHERE `id` = '.$this->rowId)->fetchColumn());
}

#[Test]
public function activatingTheModuleOnAFreshInstallCreatesItsTables(): void
{
$tables = $this->installScriptTables();

$this->forgetInstallFlag();
$this->con->exec('SET FOREIGN_KEY_CHECKS = 0');
foreach ($tables as $table) {
$this->con->exec('DROP TABLE IF EXISTS `'.$table.'`');
}
$this->con->exec('SET FOREIGN_KEY_CHECKS = 1');

$this->activate();

foreach ($tables as $table) {
self::assertTrue($this->hasTable($table), $table.' is created.');
}
}

private function activate(): void
{
$module = new ShortCode();
self::assertTrue($module->preActivation($this->con));
$module->postActivation($this->con);
}

private function forgetInstallFlag(): void
{
$moduleId = (int) ModuleQuery::create()->findOneByCode('ShortCode')?->getId();
ModuleConfigQuery::create()->filterByModuleId($moduleId)->filterByName('is_initialized')->delete();
}

/**
* @return list<string>
*/
private function installScriptTables(): array
{
preg_match_all('/^CREATE TABLE `(\w+)`/m', (string) file_get_contents(self::INSTALL_SCRIPT), $matches);

return $matches[1];
}

private function hasTable(string $table): bool
{
$statement = $this->con->prepare('SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = :table');
$statement->execute(['table' => $table]);

return (int) $statement->fetchColumn() > 0;
}
}
Loading