- Added GitHub spec-kit for development workflow - Standardized file signatures to Descomplicar® format - Updated development configuration 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
217 lines
8.1 KiB
PHP
217 lines
8.1 KiB
PHP
/**
|
|
* Descomplicar® Crescimento Digital
|
|
* https://descomplicar.pt
|
|
*/
|
|
|
|
<?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_%'");
|
|
}
|
|
} |