fix(perfexcrm module): align version to 3.0.1, unify entrypoint, and harden routes/views
- Bump DESK_MOLONI version to 3.0.1 across module - Normalize hooks to after_client_* and instantiate PerfexHooks safely - Fix OAuthController view path and API client class name - Add missing admin views for webhook config/logs; adjust view loading - Harden client portal routes and admin routes mapping - Make Dashboard/Logs/Queue tolerant to optional model methods - Align log details query with existing schema; avoid broken joins This makes the module operational in Perfex (admin + client), reduces 404s, and avoids fatal errors due to inconsistent tables/methods.
This commit is contained in:
212
modules/desk_moloni/tests/database/ConfigTableTest.php
Normal file
212
modules/desk_moloni/tests/database/ConfigTableTest.php
Normal file
@@ -0,0 +1,212 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DeskMoloni\Tests\Database;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use PDO;
|
||||
|
||||
/**
|
||||
* Contract Test: desk_moloni_config table structure and constraints
|
||||
*
|
||||
* This test MUST FAIL initially as part of TDD methodology.
|
||||
* Tests validate database schema contracts before implementation.
|
||||
*/
|
||||
class ConfigTableTest extends TestCase
|
||||
{
|
||||
private PDO $pdo;
|
||||
private string $tableName = 'tbl_desk_moloni_config';
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
global $testConfig;
|
||||
|
||||
$this->pdo = new PDO(
|
||||
"mysql:host={$testConfig['database']['hostname']};dbname={$testConfig['database']['database']}",
|
||||
$testConfig['database']['username'],
|
||||
$testConfig['database']['password'],
|
||||
[
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function testTableExists(): void
|
||||
{
|
||||
$stmt = $this->pdo->query("SHOW TABLES LIKE '{$this->tableName}'");
|
||||
$result = $stmt->fetch();
|
||||
|
||||
$this->assertNotFalse($result, "Table {$this->tableName} must exist");
|
||||
}
|
||||
|
||||
public function testTableStructureContract(): void
|
||||
{
|
||||
$stmt = $this->pdo->query("DESCRIBE {$this->tableName}");
|
||||
$columns = $stmt->fetchAll();
|
||||
|
||||
$expectedColumns = [
|
||||
'id' => ['Type' => 'int', 'Null' => 'NO', 'Key' => 'PRI', 'Extra' => 'auto_increment'],
|
||||
'setting_key' => ['Type' => 'varchar(255)', 'Null' => 'NO', 'Key' => 'UNI'],
|
||||
'setting_value' => ['Type' => 'text', 'Null' => 'YES'],
|
||||
'encrypted' => ['Type' => 'tinyint(1)', 'Null' => 'YES', 'Default' => '0'],
|
||||
'created_at' => ['Type' => 'timestamp', 'Null' => 'NO', 'Default' => 'CURRENT_TIMESTAMP'],
|
||||
'updated_at' => ['Type' => 'timestamp', 'Null' => 'NO', 'Default' => 'CURRENT_TIMESTAMP']
|
||||
];
|
||||
|
||||
$actualColumns = [];
|
||||
foreach ($columns as $column) {
|
||||
$actualColumns[$column['Field']] = [
|
||||
'Type' => $column['Type'],
|
||||
'Null' => $column['Null'],
|
||||
'Key' => $column['Key'],
|
||||
'Default' => $column['Default'],
|
||||
'Extra' => $column['Extra']
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($expectedColumns as $columnName => $expectedSpec) {
|
||||
$this->assertArrayHasKey($columnName, $actualColumns, "Column {$columnName} must exist");
|
||||
|
||||
foreach ($expectedSpec as $property => $expectedValue) {
|
||||
$this->assertEquals(
|
||||
$expectedValue,
|
||||
$actualColumns[$columnName][$property] ?? null,
|
||||
"Column {$columnName} property {$property} must match contract"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function testUniqueConstraintOnSettingKey(): void
|
||||
{
|
||||
// Insert first record
|
||||
$stmt = $this->pdo->prepare("INSERT INTO {$this->tableName} (setting_key, setting_value) VALUES (?, ?)");
|
||||
$stmt->execute(['test_unique_key', 'test_value']);
|
||||
|
||||
// Attempt to insert duplicate key should fail
|
||||
$this->expectException(\PDOException::class);
|
||||
$this->expectExceptionMessage('Duplicate entry');
|
||||
|
||||
$stmt->execute(['test_unique_key', 'another_value']);
|
||||
}
|
||||
|
||||
public function testEncryptionFlagValidation(): void
|
||||
{
|
||||
$stmt = $this->pdo->prepare("INSERT INTO {$this->tableName} (setting_key, setting_value, encrypted) VALUES (?, ?, ?)");
|
||||
|
||||
// Valid encryption flag values
|
||||
$stmt->execute(['test_encrypted_1', 'encrypted_value', 1]);
|
||||
$stmt->execute(['test_encrypted_0', 'plain_value', 0]);
|
||||
|
||||
// Verify encryption flag is stored correctly
|
||||
$stmt = $this->pdo->query("SELECT setting_key, encrypted FROM {$this->tableName} WHERE setting_key IN ('test_encrypted_1', 'test_encrypted_0')");
|
||||
$results = $stmt->fetchAll();
|
||||
|
||||
$this->assertCount(2, $results);
|
||||
|
||||
foreach ($results as $result) {
|
||||
if ($result['setting_key'] === 'test_encrypted_1') {
|
||||
$this->assertEquals(1, $result['encrypted']);
|
||||
} else {
|
||||
$this->assertEquals(0, $result['encrypted']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function testTimestampAutomaticUpdates(): void
|
||||
{
|
||||
// Insert record
|
||||
$stmt = $this->pdo->prepare("INSERT INTO {$this->tableName} (setting_key, setting_value) VALUES (?, ?)");
|
||||
$stmt->execute(['test_timestamp', 'initial_value']);
|
||||
|
||||
// Get initial timestamps
|
||||
$stmt = $this->pdo->query("SELECT created_at, updated_at FROM {$this->tableName} WHERE setting_key = 'test_timestamp'");
|
||||
$initial = $stmt->fetch();
|
||||
|
||||
// Wait a moment and update
|
||||
sleep(1);
|
||||
$stmt = $this->pdo->prepare("UPDATE {$this->tableName} SET setting_value = ? WHERE setting_key = ?");
|
||||
$stmt->execute(['updated_value', 'test_timestamp']);
|
||||
|
||||
// Get updated timestamps
|
||||
$stmt = $this->pdo->query("SELECT created_at, updated_at FROM {$this->tableName} WHERE setting_key = 'test_timestamp'");
|
||||
$updated = $stmt->fetch();
|
||||
|
||||
// created_at should remain the same
|
||||
$this->assertEquals($initial['created_at'], $updated['created_at']);
|
||||
|
||||
// updated_at should be different
|
||||
$this->assertNotEquals($initial['updated_at'], $updated['updated_at']);
|
||||
$this->assertGreaterThan($initial['updated_at'], $updated['updated_at']);
|
||||
}
|
||||
|
||||
public function testRequiredIndexesExist(): void
|
||||
{
|
||||
$stmt = $this->pdo->query("SHOW INDEX FROM {$this->tableName}");
|
||||
$indexes = $stmt->fetchAll();
|
||||
|
||||
$indexNames = array_column($indexes, 'Key_name');
|
||||
|
||||
// Required indexes based on schema
|
||||
$expectedIndexes = ['PRIMARY', 'setting_key', 'idx_setting_key', 'idx_encrypted'];
|
||||
|
||||
foreach ($expectedIndexes as $expectedIndex) {
|
||||
$this->assertContains(
|
||||
$expectedIndex,
|
||||
$indexNames,
|
||||
"Index {$expectedIndex} must exist for performance"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function testSettingValueCanStoreJson(): void
|
||||
{
|
||||
$jsonData = json_encode([
|
||||
'complex' => 'data',
|
||||
'with' => ['nested', 'arrays'],
|
||||
'and' => 123,
|
||||
'numbers' => true
|
||||
]);
|
||||
|
||||
$stmt = $this->pdo->prepare("INSERT INTO {$this->tableName} (setting_key, setting_value) VALUES (?, ?)");
|
||||
$stmt->execute(['test_json', $jsonData]);
|
||||
|
||||
$stmt = $this->pdo->query("SELECT setting_value FROM {$this->tableName} WHERE setting_key = 'test_json'");
|
||||
$result = $stmt->fetch();
|
||||
|
||||
$this->assertEquals($jsonData, $result['setting_value']);
|
||||
$this->assertIsArray(json_decode($result['setting_value'], true));
|
||||
}
|
||||
|
||||
public function testEncryptedSettingsHandling(): void
|
||||
{
|
||||
// Simulate encrypted data storage
|
||||
$plaintext = 'sensitive_api_key_value';
|
||||
$encryptedData = base64_encode(openssl_encrypt(
|
||||
$plaintext,
|
||||
'AES-256-GCM',
|
||||
'test_encryption_key_32_characters',
|
||||
0,
|
||||
'test_iv_12bytes'
|
||||
));
|
||||
|
||||
$stmt = $this->pdo->prepare("INSERT INTO {$this->tableName} (setting_key, setting_value, encrypted) VALUES (?, ?, ?)");
|
||||
$stmt->execute(['oauth_access_token', $encryptedData, 1]);
|
||||
|
||||
// Verify encrypted flag is set and data is stored
|
||||
$stmt = $this->pdo->query("SELECT setting_value, encrypted FROM {$this->tableName} WHERE setting_key = 'oauth_access_token'");
|
||||
$result = $stmt->fetch();
|
||||
|
||||
$this->assertEquals(1, $result['encrypted']);
|
||||
$this->assertEquals($encryptedData, $result['setting_value']);
|
||||
$this->assertNotEquals($plaintext, $result['setting_value']);
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
// Clean up test data
|
||||
$this->pdo->exec("DELETE FROM {$this->tableName} WHERE setting_key LIKE 'test_%'");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user