Files
desk-moloni/modules/desk_moloni/tests/database/LogTableTest.php
Emanuel Almeida 8c4f68576f chore: add spec-kit and standardize signatures
- 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>
2025-09-12 01:27:37 +01:00

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');
}
}