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:
289
modules/desk_moloni/tests/unit/ConfigModelTest.php
Normal file
289
modules/desk_moloni/tests/unit/ConfigModelTest.php
Normal file
@@ -0,0 +1,289 @@
|
||||
<?php
|
||||
/**
|
||||
* Unit Test for Config_model
|
||||
*
|
||||
* This test MUST FAIL until the Config_model is properly implemented
|
||||
* Following TDD RED-GREEN-REFACTOR cycle
|
||||
*
|
||||
* @package DeskMoloni\Tests\Unit
|
||||
*/
|
||||
|
||||
namespace DeskMoloni\Tests\Unit;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ConfigModelTest extends TestCase
|
||||
{
|
||||
private $CI;
|
||||
private $config_model;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
// Initialize CodeIgniter instance
|
||||
$this->CI = &get_instance();
|
||||
|
||||
// Ensure we're in test environment
|
||||
if (ENVIRONMENT !== 'testing') {
|
||||
$this->markTestSkipped('Unit tests should only run in testing environment');
|
||||
}
|
||||
|
||||
// This will FAIL until Config_model is implemented
|
||||
$this->CI->load->model('desk_moloni/config_model');
|
||||
$this->config_model = $this->CI->config_model;
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* Contract: Config model must be loadable and inherit from CI_Model
|
||||
*/
|
||||
public function config_model_exists_and_is_valid()
|
||||
{
|
||||
// ASSERT: Model must be loaded successfully
|
||||
$this->assertNotNull($this->config_model, 'Config_model must be loadable');
|
||||
$this->assertInstanceOf('CI_Model', $this->config_model, 'Config_model must inherit from CI_Model');
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* Contract: Config model must provide method to get configuration value
|
||||
*/
|
||||
public function config_model_can_get_configuration_values()
|
||||
{
|
||||
// ARRANGE: Ensure method exists
|
||||
$this->assertTrue(method_exists($this->config_model, 'get'), 'Config_model must have get() method');
|
||||
|
||||
// ACT: Try to get a configuration value
|
||||
$result = $this->config_model->get('module_version');
|
||||
|
||||
// ASSERT: Method must return value or null
|
||||
$this->assertTrue(is_string($result) || is_null($result), 'get() method must return string or null');
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* Contract: Config model must provide method to set configuration value
|
||||
*/
|
||||
public function config_model_can_set_configuration_values()
|
||||
{
|
||||
// ARRANGE: Ensure method exists
|
||||
$this->assertTrue(method_exists($this->config_model, 'set'), 'Config_model must have set() method');
|
||||
|
||||
// ACT: Try to set a configuration value
|
||||
$test_key = 'test_config_key';
|
||||
$test_value = 'test_config_value';
|
||||
$result = $this->config_model->set($test_key, $test_value);
|
||||
|
||||
// ASSERT: Method must return boolean success indicator
|
||||
$this->assertIsBool($result, 'set() method must return boolean');
|
||||
$this->assertTrue($result, 'set() method must return true on success');
|
||||
|
||||
// ASSERT: Value must be retrievable
|
||||
$retrieved_value = $this->config_model->get($test_key);
|
||||
$this->assertEquals($test_value, $retrieved_value, 'Set value must be retrievable');
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* Contract: Config model must support encrypted configuration storage
|
||||
*/
|
||||
public function config_model_supports_encrypted_storage()
|
||||
{
|
||||
// ARRANGE: Ensure method exists
|
||||
$this->assertTrue(method_exists($this->config_model, 'set_encrypted'), 'Config_model must have set_encrypted() method');
|
||||
$this->assertTrue(method_exists($this->config_model, 'get_encrypted'), 'Config_model must have get_encrypted() method');
|
||||
|
||||
// ACT: Set encrypted value
|
||||
$test_key = 'test_encrypted_key';
|
||||
$test_value = 'sensitive_data_123';
|
||||
$set_result = $this->config_model->set_encrypted($test_key, $test_value);
|
||||
|
||||
// ASSERT: Encrypted set must succeed
|
||||
$this->assertTrue($set_result, 'set_encrypted() must return true on success');
|
||||
|
||||
// ACT: Get encrypted value
|
||||
$retrieved_value = $this->config_model->get_encrypted($test_key);
|
||||
|
||||
// ASSERT: Encrypted value must be retrievable and match
|
||||
$this->assertEquals($test_value, $retrieved_value, 'Encrypted value must be retrievable and decrypted correctly');
|
||||
|
||||
// ASSERT: Raw stored value must be different (encrypted)
|
||||
$raw_value = $this->config_model->get($test_key);
|
||||
$this->assertNotEquals($test_value, $raw_value, 'Raw stored value must be encrypted');
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* Contract: Config model must support OAuth token storage with expiration
|
||||
*/
|
||||
public function config_model_supports_oauth_token_storage()
|
||||
{
|
||||
// ARRANGE: Ensure methods exist
|
||||
$this->assertTrue(method_exists($this->config_model, 'set_oauth_token'), 'Config_model must have set_oauth_token() method');
|
||||
$this->assertTrue(method_exists($this->config_model, 'get_oauth_token'), 'Config_model must have get_oauth_token() method');
|
||||
$this->assertTrue(method_exists($this->config_model, 'is_oauth_token_valid'), 'Config_model must have is_oauth_token_valid() method');
|
||||
|
||||
// ACT: Set OAuth token with expiration
|
||||
$token = 'test_oauth_token_123';
|
||||
$expires_at = time() + 3600; // 1 hour from now
|
||||
$set_result = $this->config_model->set_oauth_token($token, $expires_at);
|
||||
|
||||
// ASSERT: Token set must succeed
|
||||
$this->assertTrue($set_result, 'set_oauth_token() must return true on success');
|
||||
|
||||
// ACT: Get OAuth token
|
||||
$token_data = $this->config_model->get_oauth_token();
|
||||
|
||||
// ASSERT: Token data must be valid array
|
||||
$this->assertIsArray($token_data, 'get_oauth_token() must return array');
|
||||
$this->assertArrayHasKey('token', $token_data, 'Token data must have token key');
|
||||
$this->assertArrayHasKey('expires_at', $token_data, 'Token data must have expires_at key');
|
||||
$this->assertEquals($token, $token_data['token'], 'Token must match stored value');
|
||||
|
||||
// ACT: Check token validity
|
||||
$is_valid = $this->config_model->is_oauth_token_valid();
|
||||
|
||||
// ASSERT: Token must be valid (not expired)
|
||||
$this->assertTrue($is_valid, 'OAuth token must be valid when not expired');
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* Contract: Config model must handle expired OAuth tokens
|
||||
*/
|
||||
public function config_model_handles_expired_oauth_tokens()
|
||||
{
|
||||
// ARRANGE: Set expired token
|
||||
$token = 'expired_token_123';
|
||||
$expires_at = time() - 3600; // 1 hour ago (expired)
|
||||
$this->config_model->set_oauth_token($token, $expires_at);
|
||||
|
||||
// ACT: Check token validity
|
||||
$is_valid = $this->config_model->is_oauth_token_valid();
|
||||
|
||||
// ASSERT: Expired token must be invalid
|
||||
$this->assertFalse($is_valid, 'Expired OAuth token must be invalid');
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* Contract: Config model must provide method to get all configuration
|
||||
*/
|
||||
public function config_model_can_get_all_configuration()
|
||||
{
|
||||
// ARRANGE: Ensure method exists
|
||||
$this->assertTrue(method_exists($this->config_model, 'get_all'), 'Config_model must have get_all() method');
|
||||
|
||||
// ACT: Get all configuration
|
||||
$all_config = $this->config_model->get_all();
|
||||
|
||||
// ASSERT: Must return array
|
||||
$this->assertIsArray($all_config, 'get_all() must return array');
|
||||
|
||||
// ASSERT: Must contain default configuration values
|
||||
$this->assertArrayHasKey('module_version', $all_config, 'Configuration must contain module_version');
|
||||
$this->assertEquals('3.0.0', $all_config['module_version'], 'Module version must be 3.0.0');
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* Contract: Config model must support configuration deletion
|
||||
*/
|
||||
public function config_model_can_delete_configuration()
|
||||
{
|
||||
// ARRANGE: Set test configuration
|
||||
$test_key = 'test_delete_key';
|
||||
$test_value = 'test_delete_value';
|
||||
$this->config_model->set($test_key, $test_value);
|
||||
|
||||
// ARRANGE: Ensure method exists
|
||||
$this->assertTrue(method_exists($this->config_model, 'delete'), 'Config_model must have delete() method');
|
||||
|
||||
// ACT: Delete configuration
|
||||
$delete_result = $this->config_model->delete($test_key);
|
||||
|
||||
// ASSERT: Delete must succeed
|
||||
$this->assertTrue($delete_result, 'delete() must return true on success');
|
||||
|
||||
// ASSERT: Value must no longer exist
|
||||
$retrieved_value = $this->config_model->get($test_key);
|
||||
$this->assertNull($retrieved_value, 'Deleted configuration must return null');
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* Contract: Config model must validate configuration keys
|
||||
*/
|
||||
public function config_model_validates_configuration_keys()
|
||||
{
|
||||
// ACT & ASSERT: Empty key must be invalid
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
$this->config_model->set('', 'test_value');
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* Contract: Config model must handle database errors gracefully
|
||||
*/
|
||||
public function config_model_handles_database_errors_gracefully()
|
||||
{
|
||||
// ARRANGE: Ensure method exists
|
||||
$this->assertTrue(method_exists($this->config_model, 'get'), 'Config_model must have get() method');
|
||||
|
||||
// ACT: Try to get non-existent configuration
|
||||
$result = $this->config_model->get('non_existent_key_12345');
|
||||
|
||||
// ASSERT: Must return null for non-existent keys
|
||||
$this->assertNull($result, 'Non-existent configuration must return null');
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* Contract: Config model must support batch operations
|
||||
*/
|
||||
public function config_model_supports_batch_operations()
|
||||
{
|
||||
// ARRANGE: Ensure method exists
|
||||
$this->assertTrue(method_exists($this->config_model, 'set_batch'), 'Config_model must have set_batch() method');
|
||||
|
||||
// ACT: Set multiple configurations
|
||||
$batch_config = [
|
||||
'batch_test_1' => 'value_1',
|
||||
'batch_test_2' => 'value_2',
|
||||
'batch_test_3' => 'value_3',
|
||||
];
|
||||
|
||||
$batch_result = $this->config_model->set_batch($batch_config);
|
||||
|
||||
// ASSERT: Batch set must succeed
|
||||
$this->assertTrue($batch_result, 'set_batch() must return true on success');
|
||||
|
||||
// ASSERT: All values must be retrievable
|
||||
foreach ($batch_config as $key => $expected_value) {
|
||||
$actual_value = $this->config_model->get($key);
|
||||
$this->assertEquals($expected_value, $actual_value, "Batch set value for '{$key}' must be retrievable");
|
||||
}
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
// Clean up test configuration data
|
||||
if ($this->config_model) {
|
||||
$test_keys = [
|
||||
'test_config_key',
|
||||
'test_encrypted_key',
|
||||
'test_delete_key',
|
||||
'batch_test_1',
|
||||
'batch_test_2',
|
||||
'batch_test_3'
|
||||
];
|
||||
|
||||
foreach ($test_keys as $key) {
|
||||
try {
|
||||
$this->config_model->delete($key);
|
||||
} catch (Exception $e) {
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user