- 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>
592 lines
21 KiB
PHP
592 lines
21 KiB
PHP
/**
|
|
* Descomplicar® Crescimento Digital
|
|
* https://descomplicar.pt
|
|
*/
|
|
|
|
<?php
|
|
|
|
/**
|
|
* LogTableTest.php
|
|
*
|
|
* PHPUnit tests for desk_moloni_sync_log table structure and validation rules
|
|
* Tests comprehensive audit log of all synchronization operations
|
|
*
|
|
* @package DeskMoloni\Tests\Database
|
|
* @author Database Design Specialist
|
|
* @version 3.0
|
|
*/
|
|
|
|
require_once(__DIR__ . '/../../../../tests/TestCase.php');
|
|
|
|
class LogTableTest extends TestCase
|
|
{
|
|
private $tableName = 'desk_moloni_sync_log';
|
|
private $testLogModel;
|
|
|
|
public function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
$this->clearTestData();
|
|
|
|
// Initialize test model (will be implemented after tests)
|
|
// $this->testLogModel = new DeskMoloniSyncLog();
|
|
}
|
|
|
|
public function tearDown(): void
|
|
{
|
|
$this->clearTestData();
|
|
parent::tearDown();
|
|
}
|
|
|
|
/**
|
|
* Test table structure exists with correct columns
|
|
*/
|
|
public function testTableStructureExists()
|
|
{
|
|
$db = $this->ci->db;
|
|
|
|
// Verify table exists
|
|
$this->assertTrue($db->table_exists($this->tableName), "Table {$this->tableName} should exist");
|
|
|
|
// Verify required columns exist
|
|
$expectedColumns = [
|
|
'id', 'operation_type', 'entity_type', 'perfex_id', 'moloni_id',
|
|
'direction', 'status', 'request_data', 'response_data', 'error_message',
|
|
'execution_time_ms', 'created_at'
|
|
];
|
|
|
|
foreach ($expectedColumns as $column) {
|
|
$this->assertTrue($db->field_exists($column, $this->tableName),
|
|
"Column '{$column}' should exist in {$this->tableName}");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test operation_type ENUM values
|
|
*/
|
|
public function testOperationTypeEnumValues()
|
|
{
|
|
$db = $this->ci->db;
|
|
|
|
$validOperationTypes = ['create', 'update', 'delete', 'status_change'];
|
|
|
|
foreach ($validOperationTypes as $operationType) {
|
|
$data = [
|
|
'operation_type' => $operationType,
|
|
'entity_type' => 'client',
|
|
'perfex_id' => rand(1, 1000),
|
|
'direction' => 'perfex_to_moloni',
|
|
'status' => 'success'
|
|
];
|
|
|
|
$this->assertTrue($db->insert($this->tableName, $data),
|
|
"Valid operation type '{$operationType}' should insert successfully");
|
|
|
|
$record = $db->where('perfex_id', $data['perfex_id'])->get($this->tableName)->row();
|
|
$this->assertEquals($operationType, $record->operation_type, "Operation type should match inserted value");
|
|
|
|
// Clean up
|
|
$db->where('perfex_id', $data['perfex_id'])->delete($this->tableName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test entity_type ENUM values
|
|
*/
|
|
public function testEntityTypeEnumValues()
|
|
{
|
|
$db = $this->ci->db;
|
|
|
|
$validEntityTypes = ['client', 'product', 'invoice', 'estimate', 'credit_note'];
|
|
|
|
foreach ($validEntityTypes as $entityType) {
|
|
$data = [
|
|
'operation_type' => 'create',
|
|
'entity_type' => $entityType,
|
|
'perfex_id' => rand(1, 1000),
|
|
'direction' => 'perfex_to_moloni',
|
|
'status' => 'success'
|
|
];
|
|
|
|
$this->assertTrue($db->insert($this->tableName, $data),
|
|
"Valid entity type '{$entityType}' should insert successfully");
|
|
|
|
// Clean up
|
|
$db->where('perfex_id', $data['perfex_id'])->delete($this->tableName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test direction ENUM values
|
|
*/
|
|
public function testDirectionEnumValues()
|
|
{
|
|
$db = $this->ci->db;
|
|
|
|
$validDirections = ['perfex_to_moloni', 'moloni_to_perfex'];
|
|
|
|
foreach ($validDirections as $direction) {
|
|
$data = [
|
|
'operation_type' => 'update',
|
|
'entity_type' => 'product',
|
|
'perfex_id' => rand(1, 1000),
|
|
'direction' => $direction,
|
|
'status' => 'success'
|
|
];
|
|
|
|
$this->assertTrue($db->insert($this->tableName, $data),
|
|
"Valid direction '{$direction}' should insert successfully");
|
|
|
|
$record = $db->where('perfex_id', $data['perfex_id'])->get($this->tableName)->row();
|
|
$this->assertEquals($direction, $record->direction, "Direction should match inserted value");
|
|
|
|
// Clean up
|
|
$db->where('perfex_id', $data['perfex_id'])->delete($this->tableName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test status ENUM values
|
|
*/
|
|
public function testStatusEnumValues()
|
|
{
|
|
$db = $this->ci->db;
|
|
|
|
$validStatuses = ['success', 'error', 'warning'];
|
|
|
|
foreach ($validStatuses as $status) {
|
|
$data = [
|
|
'operation_type' => 'create',
|
|
'entity_type' => 'invoice',
|
|
'perfex_id' => rand(1, 1000),
|
|
'direction' => 'perfex_to_moloni',
|
|
'status' => $status
|
|
];
|
|
|
|
$this->assertTrue($db->insert($this->tableName, $data),
|
|
"Valid status '{$status}' should insert successfully");
|
|
|
|
$record = $db->where('perfex_id', $data['perfex_id'])->get($this->tableName)->row();
|
|
$this->assertEquals($status, $record->status, "Status should match inserted value");
|
|
|
|
// Clean up
|
|
$db->where('perfex_id', $data['perfex_id'])->delete($this->tableName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test entity ID constraints - at least one must be present
|
|
*/
|
|
public function testEntityIdConstraints()
|
|
{
|
|
$db = $this->ci->db;
|
|
|
|
// Test with perfex_id only
|
|
$dataWithPerfexId = [
|
|
'operation_type' => 'create',
|
|
'entity_type' => 'client',
|
|
'perfex_id' => rand(10000, 19999),
|
|
'moloni_id' => null,
|
|
'direction' => 'perfex_to_moloni',
|
|
'status' => 'success'
|
|
];
|
|
|
|
$this->assertTrue($db->insert($this->tableName, $dataWithPerfexId),
|
|
'Insert with perfex_id only should succeed');
|
|
|
|
// Test with moloni_id only
|
|
$dataWithMoloniId = [
|
|
'operation_type' => 'update',
|
|
'entity_type' => 'product',
|
|
'perfex_id' => null,
|
|
'moloni_id' => rand(10000, 19999),
|
|
'direction' => 'moloni_to_perfex',
|
|
'status' => 'success'
|
|
];
|
|
|
|
$this->assertTrue($db->insert($this->tableName, $dataWithMoloniId),
|
|
'Insert with moloni_id only should succeed');
|
|
|
|
// Test with both IDs
|
|
$dataWithBothIds = [
|
|
'operation_type' => 'update',
|
|
'entity_type' => 'invoice',
|
|
'perfex_id' => rand(20000, 29999),
|
|
'moloni_id' => rand(20000, 29999),
|
|
'direction' => 'perfex_to_moloni',
|
|
'status' => 'success'
|
|
];
|
|
|
|
$this->assertTrue($db->insert($this->tableName, $dataWithBothIds),
|
|
'Insert with both IDs should succeed');
|
|
}
|
|
|
|
/**
|
|
* Test JSON validation for request and response data
|
|
*/
|
|
public function testJSONValidation()
|
|
{
|
|
$db = $this->ci->db;
|
|
|
|
// Test valid JSON data
|
|
$validJSONData = [
|
|
'{"client_id": 123, "name": "Test Client", "email": "test@example.com"}',
|
|
'{"products": [{"id": 1, "name": "Product 1"}, {"id": 2, "name": "Product 2"}]}',
|
|
'[]',
|
|
'{}',
|
|
null
|
|
];
|
|
|
|
foreach ($validJSONData as $index => $jsonData) {
|
|
$data = [
|
|
'operation_type' => 'create',
|
|
'entity_type' => 'client',
|
|
'perfex_id' => rand(30000, 39999) + $index,
|
|
'direction' => 'perfex_to_moloni',
|
|
'status' => 'success',
|
|
'request_data' => $jsonData,
|
|
'response_data' => $jsonData
|
|
];
|
|
|
|
$this->assertTrue($db->insert($this->tableName, $data),
|
|
"Valid JSON data should insert successfully");
|
|
|
|
$record = $db->where('perfex_id', $data['perfex_id'])->get($this->tableName)->row();
|
|
$this->assertEquals($jsonData, $record->request_data, "Request data should match");
|
|
$this->assertEquals($jsonData, $record->response_data, "Response data should match");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test execution time validation
|
|
*/
|
|
public function testExecutionTimeValidation()
|
|
{
|
|
$db = $this->ci->db;
|
|
|
|
$validExecutionTimes = [0, 50, 150, 1000, 5000];
|
|
|
|
foreach ($validExecutionTimes as $index => $executionTime) {
|
|
$data = [
|
|
'operation_type' => 'update',
|
|
'entity_type' => 'product',
|
|
'perfex_id' => rand(40000, 49999) + $index,
|
|
'direction' => 'perfex_to_moloni',
|
|
'status' => 'success',
|
|
'execution_time_ms' => $executionTime
|
|
];
|
|
|
|
$this->assertTrue($db->insert($this->tableName, $data),
|
|
"Valid execution time '{$executionTime}ms' should insert successfully");
|
|
|
|
$record = $db->where('perfex_id', $data['perfex_id'])->get($this->tableName)->row();
|
|
$this->assertEquals($executionTime, $record->execution_time_ms, "Execution time should match");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test successful operation logging
|
|
*/
|
|
public function testSuccessfulOperationLogging()
|
|
{
|
|
$db = $this->ci->db;
|
|
|
|
$successfulOperation = [
|
|
'operation_type' => 'create',
|
|
'entity_type' => 'client',
|
|
'perfex_id' => rand(50000, 59999),
|
|
'moloni_id' => rand(50000, 59999),
|
|
'direction' => 'perfex_to_moloni',
|
|
'status' => 'success',
|
|
'request_data' => '{"name": "New Client", "email": "client@example.com"}',
|
|
'response_data' => '{"id": 12345, "status": "created", "moloni_id": 54321}',
|
|
'execution_time_ms' => 250
|
|
];
|
|
|
|
$this->assertTrue($db->insert($this->tableName, $successfulOperation),
|
|
'Successful operation should log correctly');
|
|
|
|
$logEntry = $db->where('perfex_id', $successfulOperation['perfex_id'])->get($this->tableName)->row();
|
|
|
|
$this->assertEquals('success', $logEntry->status, 'Status should be success');
|
|
$this->assertNull($logEntry->error_message, 'Error message should be NULL for successful operations');
|
|
$this->assertNotNull($logEntry->request_data, 'Request data should be logged');
|
|
$this->assertNotNull($logEntry->response_data, 'Response data should be logged');
|
|
$this->assertEquals(250, $logEntry->execution_time_ms, 'Execution time should be logged');
|
|
}
|
|
|
|
/**
|
|
* Test error operation logging
|
|
*/
|
|
public function testErrorOperationLogging()
|
|
{
|
|
$db = $this->ci->db;
|
|
|
|
$errorMessage = 'API returned 400 Bad Request: Invalid client data - email field is required';
|
|
|
|
$errorOperation = [
|
|
'operation_type' => 'create',
|
|
'entity_type' => 'client',
|
|
'perfex_id' => rand(60000, 69999),
|
|
'direction' => 'perfex_to_moloni',
|
|
'status' => 'error',
|
|
'request_data' => '{"name": "Incomplete Client"}',
|
|
'response_data' => '{"error": "email field is required", "code": 400}',
|
|
'error_message' => $errorMessage,
|
|
'execution_time_ms' => 1200
|
|
];
|
|
|
|
$this->assertTrue($db->insert($this->tableName, $errorOperation),
|
|
'Error operation should log correctly');
|
|
|
|
$logEntry = $db->where('perfex_id', $errorOperation['perfex_id'])->get($this->tableName)->row();
|
|
|
|
$this->assertEquals('error', $logEntry->status, 'Status should be error');
|
|
$this->assertEquals($errorMessage, $logEntry->error_message, 'Error message should be logged');
|
|
$this->assertNotNull($logEntry->request_data, 'Request data should be logged for debugging');
|
|
$this->assertNotNull($logEntry->response_data, 'Response data should be logged for debugging');
|
|
}
|
|
|
|
/**
|
|
* Test warning operation logging
|
|
*/
|
|
public function testWarningOperationLogging()
|
|
{
|
|
$db = $this->ci->db;
|
|
|
|
$warningMessage = 'Operation completed but some fields were ignored due to validation rules';
|
|
|
|
$warningOperation = [
|
|
'operation_type' => 'update',
|
|
'entity_type' => 'product',
|
|
'perfex_id' => rand(70000, 79999),
|
|
'moloni_id' => rand(70000, 79999),
|
|
'direction' => 'perfex_to_moloni',
|
|
'status' => 'warning',
|
|
'request_data' => '{"name": "Updated Product", "invalid_field": "ignored"}',
|
|
'response_data' => '{"id": 12345, "status": "updated", "warnings": ["invalid_field ignored"]}',
|
|
'error_message' => $warningMessage,
|
|
'execution_time_ms' => 800
|
|
];
|
|
|
|
$this->assertTrue($db->insert($this->tableName, $warningOperation),
|
|
'Warning operation should log correctly');
|
|
|
|
$logEntry = $db->where('perfex_id', $warningOperation['perfex_id'])->get($this->tableName)->row();
|
|
|
|
$this->assertEquals('warning', $logEntry->status, 'Status should be warning');
|
|
$this->assertEquals($warningMessage, $logEntry->error_message, 'Warning message should be logged');
|
|
}
|
|
|
|
/**
|
|
* Test performance indexes exist
|
|
*/
|
|
public function testPerformanceIndexes()
|
|
{
|
|
$db = $this->ci->db;
|
|
|
|
$query = "SHOW INDEX FROM {$this->tableName}";
|
|
$indexes = $db->query($query)->result_array();
|
|
|
|
$indexNames = array_column($indexes, 'Key_name');
|
|
|
|
// Expected indexes for log analysis and performance
|
|
$expectedIndexes = [
|
|
'PRIMARY',
|
|
'idx_entity_status',
|
|
'idx_perfex_entity',
|
|
'idx_moloni_entity',
|
|
'idx_created_at',
|
|
'idx_operation_direction',
|
|
'idx_status',
|
|
'idx_execution_time'
|
|
];
|
|
|
|
foreach ($expectedIndexes as $expectedIndex) {
|
|
$this->assertContains($expectedIndex, $indexNames,
|
|
"Index '{$expectedIndex}' should exist for log analysis performance");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test log analysis queries
|
|
*/
|
|
public function testLogAnalysisQueries()
|
|
{
|
|
$db = $this->ci->db;
|
|
|
|
// Insert test log entries for analysis
|
|
$testLogs = [
|
|
['operation_type' => 'create', 'entity_type' => 'client', 'perfex_id' => 80001, 'status' => 'success', 'execution_time_ms' => 200],
|
|
['operation_type' => 'update', 'entity_type' => 'client', 'perfex_id' => 80002, 'status' => 'error', 'execution_time_ms' => 1500],
|
|
['operation_type' => 'create', 'entity_type' => 'product', 'perfex_id' => 80003, 'status' => 'success', 'execution_time_ms' => 300],
|
|
['operation_type' => 'delete', 'entity_type' => 'invoice', 'perfex_id' => 80004, 'status' => 'success', 'execution_time_ms' => 100]
|
|
];
|
|
|
|
foreach ($testLogs as $log) {
|
|
$log['direction'] = 'perfex_to_moloni';
|
|
$db->insert($this->tableName, $log);
|
|
}
|
|
|
|
// Test error analysis query
|
|
$errorCount = $db->where('status', 'error')
|
|
->where('created_at >=', date('Y-m-d', strtotime('-1 day')))
|
|
->count_all_results($this->tableName);
|
|
|
|
$this->assertGreaterThanOrEqual(1, $errorCount, 'Should find error logs');
|
|
|
|
// Test performance analysis query
|
|
$slowOperations = $db->where('execution_time_ms >', 1000)
|
|
->order_by('execution_time_ms', 'DESC')
|
|
->get($this->tableName)
|
|
->result_array();
|
|
|
|
$this->assertGreaterThanOrEqual(1, count($slowOperations), 'Should find slow operations');
|
|
|
|
// Test entity-specific analysis
|
|
$clientOperations = $db->where('entity_type', 'client')
|
|
->where('created_at >=', date('Y-m-d'))
|
|
->get($this->tableName)
|
|
->result_array();
|
|
|
|
$this->assertGreaterThanOrEqual(2, count($clientOperations), 'Should find client operations');
|
|
}
|
|
|
|
/**
|
|
* Test timestamp auto-population
|
|
*/
|
|
public function testTimestampAutoPopulation()
|
|
{
|
|
$db = $this->ci->db;
|
|
|
|
$beforeInsert = time();
|
|
|
|
$data = [
|
|
'operation_type' => 'status_change',
|
|
'entity_type' => 'invoice',
|
|
'perfex_id' => rand(90000, 99999),
|
|
'direction' => 'moloni_to_perfex',
|
|
'status' => 'success'
|
|
];
|
|
|
|
$this->assertTrue($db->insert($this->tableName, $data), 'Insert should succeed');
|
|
|
|
$afterInsert = time();
|
|
|
|
$record = $db->where('perfex_id', $data['perfex_id'])->get($this->tableName)->row();
|
|
|
|
// Verify created_at is populated
|
|
$this->assertNotNull($record->created_at, 'created_at should be auto-populated');
|
|
$createdTimestamp = strtotime($record->created_at);
|
|
$this->assertGreaterThanOrEqual($beforeInsert, $createdTimestamp, 'created_at should be recent');
|
|
$this->assertLessThanOrEqual($afterInsert, $createdTimestamp, 'created_at should not be in future');
|
|
}
|
|
|
|
/**
|
|
* Test audit trail completeness
|
|
*/
|
|
public function testAuditTrailCompleteness()
|
|
{
|
|
$db = $this->ci->db;
|
|
|
|
// Simulate complete operation audit trail
|
|
$operationId = rand(100000, 199999);
|
|
|
|
$auditTrail = [
|
|
[
|
|
'operation_type' => 'create',
|
|
'entity_type' => 'client',
|
|
'perfex_id' => $operationId,
|
|
'direction' => 'perfex_to_moloni',
|
|
'status' => 'success',
|
|
'request_data' => '{"name": "Audit Test Client", "email": "audit@test.com"}',
|
|
'response_data' => '{"id": ' . $operationId . ', "moloni_id": ' . ($operationId + 1000) . '}',
|
|
'execution_time_ms' => 300
|
|
],
|
|
[
|
|
'operation_type' => 'update',
|
|
'entity_type' => 'client',
|
|
'perfex_id' => $operationId,
|
|
'moloni_id' => $operationId + 1000,
|
|
'direction' => 'perfex_to_moloni',
|
|
'status' => 'success',
|
|
'request_data' => '{"name": "Updated Audit Test Client"}',
|
|
'response_data' => '{"id": ' . ($operationId + 1000) . ', "status": "updated"}',
|
|
'execution_time_ms' => 200
|
|
]
|
|
];
|
|
|
|
foreach ($auditTrail as $entry) {
|
|
$this->assertTrue($db->insert($this->tableName, $entry), 'Audit entry should insert');
|
|
}
|
|
|
|
// Verify complete audit trail exists
|
|
$auditEntries = $db->where('perfex_id', $operationId)
|
|
->order_by('created_at', 'ASC')
|
|
->get($this->tableName)
|
|
->result_array();
|
|
|
|
$this->assertEquals(2, count($auditEntries), 'Should have complete audit trail');
|
|
$this->assertEquals('create', $auditEntries[0]['operation_type'], 'First entry should be create');
|
|
$this->assertEquals('update', $auditEntries[1]['operation_type'], 'Second entry should be update');
|
|
}
|
|
|
|
/**
|
|
* Helper method to clear test data
|
|
*/
|
|
private function clearTestData()
|
|
{
|
|
$db = $this->ci->db;
|
|
|
|
// Clear test data using wide ID ranges
|
|
$idRanges = [
|
|
['min' => 1, 'max' => 199999] // Covers all test ranges
|
|
];
|
|
|
|
foreach ($idRanges as $range) {
|
|
$db->where('perfex_id >=', $range['min'])
|
|
->where('perfex_id <=', $range['max'])
|
|
->delete($this->tableName);
|
|
|
|
$db->where('moloni_id >=', $range['min'])
|
|
->where('moloni_id <=', $range['max'])
|
|
->delete($this->tableName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test character set and collation
|
|
*/
|
|
public function testCharacterSetAndCollation()
|
|
{
|
|
$db = $this->ci->db;
|
|
|
|
$query = "SELECT TABLE_COLLATION
|
|
FROM information_schema.TABLES
|
|
WHERE TABLE_SCHEMA = DATABASE()
|
|
AND TABLE_NAME = '{$this->tableName}'";
|
|
|
|
$result = $db->query($query)->row();
|
|
|
|
$this->assertEquals('utf8mb4_unicode_ci', $result->TABLE_COLLATION,
|
|
'Table should use utf8mb4_unicode_ci collation for proper Unicode support');
|
|
}
|
|
|
|
/**
|
|
* Test storage engine
|
|
*/
|
|
public function testStorageEngine()
|
|
{
|
|
$db = $this->ci->db;
|
|
|
|
$query = "SELECT ENGINE
|
|
FROM information_schema.TABLES
|
|
WHERE TABLE_SCHEMA = DATABASE()
|
|
AND TABLE_NAME = '{$this->tableName}'";
|
|
|
|
$result = $db->query($query)->row();
|
|
|
|
$this->assertEquals('InnoDB', $result->ENGINE,
|
|
'Table should use InnoDB engine for ACID compliance and audit integrity');
|
|
}
|
|
} |