AMS_Backend/tests/unit/CustomerTest.php
2025-11-06 13:41:06 +08:00

630 lines
24 KiB
PHP

<?php
namespace App\Tests\Controllers\Backend;
use App\Controllers\Backend\CustomerController;
use App\Models\Customer;
use App\Models\SettingCustomerType;
use App\Models\SettingMembershipTier;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\HTTP\IncomingRequest;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\HTTP\URI;
use Config\App;
use Config\Services;
class CustomerTest extends CIUnitTestCase
{
protected $controller;
protected $request;
protected $response;
protected $customerModel;
protected $customerTypeModel;
protected $tierModel;
protected function setUp(): void
{
parent::setUp();
// Mock models
$this->customerModel = $this->getMockBuilder(Customer::class)
->onlyMethods(['insert', 'getInsertID', 'update'])
->getMock();
$this->customerTypeModel = $this->getMockBuilder(SettingCustomerType::class)
->onlyMethods(['first'])
->addMethods(['where'])
->getMock();
$this->tierModel = $this->getMockBuilder(SettingMembershipTier::class)
->onlyMethods(['first'])
->addMethods(['where'])
->getMock();
// Set global POST input before request is retrieved
service('request')->setGlobal('post', [
'customer_type' => 'Regular',
'customer_tier' => 'Bronze',
'birthday' => '1990-01-01',
'phone' => '0123456789',
'email' => 'test@example.com',
'password_hash' => 'secret1234',
'name' => 'John Doe',
]);
// Get request service AFTER setting global input
$this->request = service('request');
$this->response = service('response');
// Now init the controller with the correct request/response
$this->controller = new \App\Controllers\Backend\CustomerController();
$this->controller->initController($this->request, $this->response, service('logger'));
// Inject mocks
$this->setProtectedOrPrivateProperty($this->controller, 'customerModel', $this->customerModel);
$this->setProtectedOrPrivateProperty($this->controller, 'customerTypeModel', $this->customerTypeModel);
$this->setProtectedOrPrivateProperty($this->controller, 'membershipTierModel', $this->tierModel);
}
//Have an issue
protected function setProtectedOrPrivateProperty($object, $property, $value)
{
$reflection = new \ReflectionObject($object);
while (!$reflection->hasProperty($property)) {
$reflection = $reflection->getParentClass();
if (!$reflection) {
throw new \Exception("Property {$property} does not exist");
}
}
$prop = $reflection->getProperty($property);
$prop->setAccessible(true);
$prop->setValue($object, $value);
}
public function testCreateFailsValidation()
{
$this->request->setGlobal('post', [
'customer_type' => 'Regular',
'customer_tier' => 'Bronze',
'birthday' => '1990-01-01',
'phone' => '0123456789',
'email' => 'invalid-email', // Invalid email format
'password_hash' => 'secret1234',
// 'name' is missing
]);
$this->customerTypeModel->method('where')->willReturnSelf();
$this->customerTypeModel->method('first')->willReturn(['id' => 1, 'name' => 'Regular']);
$this->tierModel->method('where')->willReturnSelf();
$this->tierModel->method('first')->willReturn(['id' => 2, 'name' => 'Bronze']);
$this->setProtectedOrPrivateProperty($this->controller, 'request', $this->request);
$response = $this->controller->create();
$this->assertEquals(400, $response->getStatusCode());
}
public function testCreateFailsIfCustomerTypeNotFound()
{
// Inject mock validation to bypass default validation rules
$mockValidation = $this->getMockBuilder(\CodeIgniter\Validation\Validation::class)
->disableOriginalConstructor()
->onlyMethods(['run', 'getErrors'])
->getMock();
$mockValidation->method('run')->willReturn(true);
$mockValidation->method('getErrors')->willReturn([]);
\Config\Services::injectMock('validation', $mockValidation);
// $this->request = service('request');
$this->request->setGlobal('post', [
'customer_type' => 'NonexistentType',
// 'customer_tier' => 'Bronze',
'birthday' => '1990-01-01',
'phone' => '0123456789',
'email' => 'test@example.com',
'password_hash' => 'secret1234',
'name' => 'John Doe',
]);
$this->customerTypeModel->method('where')->willReturnSelf();
$this->customerTypeModel->method('first')->willReturn(null);
$this->setProtectedOrPrivateProperty($this->controller, 'request', $this->request);
$response = $this->controller->create();
$this->assertEquals(404, $response->getStatusCode());
// $this->assertStringContainsString('Customer type', $response->getBody());
}
public function testCreateFailsIfMembershipTierNotFound()
{
$mockValidation = $this->getMockBuilder(\CodeIgniter\Validation\Validation::class)
->disableOriginalConstructor()
->onlyMethods(['run', 'getErrors'])
->getMock();
$mockValidation->method('run')->willReturn(true);
$mockValidation->method('getErrors')->willReturn([]);
\Config\Services::injectMock('validation', $mockValidation);
$this->request->setGlobal('post', [
'customer_type' => 'Regular',
'customer_tier' => 'NonexistentTier',
'birthday' => '1990-01-01',
'phone' => '0123456789',
'email' => 'test@example.com',
'password_hash' => 'secret1234',
'name' => 'John Doe',
]);
$this->customerTypeModel->method('where')->willReturnSelf();
$this->customerTypeModel->method('first')->willReturn(['id' => 1, 'name' => 'Regular']);
$this->tierModel->method('where')->willReturnSelf();
$this->tierModel->method('first')->willReturn(null);
$response = $this->controller->create();
$this->assertEquals(404, $response->getStatusCode());
}
public function testCreateCustomerSuccess()
{
$mockValidation = $this->getMockBuilder(\CodeIgniter\Validation\Validation::class)
->disableOriginalConstructor()
->onlyMethods(['run', 'getErrors'])
->getMock();
$mockValidation->method('run')->willReturn(true);
$mockValidation->method('getErrors')->willReturn([]);
\Config\Services::injectMock('validation', $mockValidation);
$this->request->setGlobal('post', [
'customer_type' => 'Regular',
'customer_tier' => 'Bronze',
'birthday' => '1990-01-01',
'phone' => '0123456789',
'email' => 'test@example.com',
'password_hash' => 'secret1234',
'name' => 'John Doe',
]);
$this->customerTypeModel->method('where')->willReturnSelf();
$this->customerTypeModel->method('first')->willReturn(['id' => 1, 'name' => 'Regular']);
$this->tierModel->method('where')->willReturnSelf();
$this->tierModel->method('first')->willReturn(['id' => 2, 'name' => 'Bronze']);
$this->customerModel->method('insert')->willReturn(true);
$this->customerModel->method('getInsertID')->willReturn(5);
$this->customerModel->method('update')->willReturn(true);
// Inject a real response object
$this->setProtectedOrPrivateProperty($this->controller, 'customerModel', $this->customerModel);
$this->setProtectedOrPrivateProperty($this->controller, 'customerTypeModel', $this->customerTypeModel);
$this->setProtectedOrPrivateProperty($this->controller, 'membershipTierModel', $this->tierModel);
$this->setProtectedOrPrivateProperty($this->controller, 'request', $this->request);
$this->setProtectedOrPrivateProperty($this->controller, 'response', service('response'));
$response = $this->controller->create();
// var_dump($response->getStatusCode());
// var_dump($response->getBody());
$this->assertEquals(200, $response->getStatusCode());
$this->assertStringContainsString('Customer created successfully', $response->getBody());
$_POST = [];
}
public function testCreateFailsOnCustomerInsert()
{
$mockValidation = $this->getMockBuilder(\CodeIgniter\Validation\Validation::class)
->disableOriginalConstructor()
->onlyMethods(['run', 'getErrors'])
->getMock();
$mockValidation->method('run')->willReturn(true);
$mockValidation->method('getErrors')->willReturn([]);
\Config\Services::injectMock('validation', $mockValidation);
$this->request->setGlobal('post', [
'customer_type' => 'Regular',
'customer_tier' => 'Bronze',
'birthday' => '1990-01-01',
'phone' => '0123456789',
'email' => 'test@example.com',
'password_hash' => 'secret1234',
'name' => 'John Doe',
]);
// Mock success on lookups
$this->customerTypeModel->method('where')->willReturnSelf();
$this->customerTypeModel->method('first')->willReturn(['id' => 1, 'name' => 'Regular']);
$this->tierModel->method('where')->willReturnSelf();
$this->tierModel->method('first')->willReturn(['id' => 2, 'name' => 'Bronze']);
// Simulate failure in DB insert
$this->customerModel->method('insert')->willReturn(false);
$this->setProtectedOrPrivateProperty($this->controller, 'request', $this->request);
$response = $this->controller->create();
$this->assertEquals(500, $response->getStatusCode());
$this->assertStringContainsString('Failed to create customer', $response->getBody());
}
public function testIndexReturnsCustomers()
{
$mockData = [
[
'id' => 1,
'name' => 'Test Customer',
'customer_tier' => 'Gold',
],
[
'id' => 2,
'name' => 'Second Customer',
'customer_tier' => 'Silver',
]
];
$this->customerModel = $this->getMockBuilder(Customer::class)
->onlyMethods(['findAll'])
->addMethods(['select', 'join'])
->getMock();
$this->customerModel->method('select')->willReturnSelf();
$this->customerModel->method('join')->willReturnSelf();
$this->customerModel->method('findAll')->willReturn($mockData);
$this->setProtectedOrPrivateProperty($this->controller, 'customerModel', $this->customerModel);
$response = $this->controller->index();
$this->assertSame(200, $response->getStatusCode());
$output = json_decode($response->getBody(), true);
$this->assertEquals('success', $output['status']);
$this->assertEquals('Customer list retrieved successfully.', $output['message']);
$this->assertCount(2, $output['data']);
$this->assertEquals('Test Customer', $output['data'][0]['name']);
}
public function testShowReturnsCustomerSuccess()
{
$mockCustomer = [
'id' => 1,
'name' => 'Test Customer',
'email' => 'test@example.com',
'profile_picture' => null,
'customer_tier' => 'Gold',
];
$this->customerModel = $this->getMockBuilder(Customer::class)
->onlyMethods(['first'])
->addMethods(['select', 'join', 'where'])
->getMock();
$this->customerModel->method('select')->willReturnSelf();
$this->customerModel->method('join')->willReturnSelf();
$this->customerModel->method('where')->willReturnSelf();
$this->customerModel->method('first')->willReturn($mockCustomer);
$this->setProtectedOrPrivateProperty($this->controller, 'customerModel', $this->customerModel);
$response = $this->controller->show(1);
$this->assertSame(200, $response->getStatusCode());
$output = json_decode($response->getBody(), true);
$this->assertEquals('success', $output['status']);
$this->assertEquals('Customer retrieved successfully.', $output['message']);
$this->assertEquals('Test Customer', $output['data']['name']);
}
public function testShowReturnsValidationErrorWhenIdMissing()
{
$this->setProtectedOrPrivateProperty($this->controller, 'response', service('response'));
$response = $this->controller->show(null);
$this->assertEquals(422, $response->getStatusCode());
$this->assertStringContainsString('Customer ID is required.', $response->getBody());
}
public function testShowReturnsNotFoundWhenCustomerMissing()
{
$this->customerModel = $this->getMockBuilder(Customer::class)
->onlyMethods(['first'])
->addMethods(['select', 'join', 'where'])
->getMock();
$this->customerModel->method('select')->willReturnSelf();
$this->customerModel->method('join')->willReturnSelf();
$this->customerModel->method('where')->willReturnSelf();
$this->customerModel->method('first')->willReturn(null);
$this->setProtectedOrPrivateProperty($this->controller, 'customerModel', $this->customerModel);
$response = $this->controller->show(99); // non-existing
$this->assertSame(404, $response->getStatusCode());
$output = json_decode($response->getBody(), true);
$this->assertEquals('error', $output['status']);
$this->assertEquals('Customer with ID 99 not found.', $output['message']);
}
public function testDeleteCustomerFailsWithoutId()
{
$this->controller = new CustomerController();
$this->controller->initController(service('request'), service('response'), service('logger'));
$result = $this->controller->delete(null);
$output = json_decode($result->getJSON(), true);
$this->assertEquals(422, $result->getStatusCode());
$this->assertEquals('error', $output['status']);
$this->assertEquals('Customer ID is required.', $output['message']);
$this->assertNull($output['data']);
}
public function testDeleteCustomerFailsWhenNotFound()
{
$this->customerModel = $this->getMockBuilder(Customer::class)
->onlyMethods(['find'])
->getMock();
$this->customerModel->method('find')->willReturn(null);
$this->controller = new CustomerController();
$this->controller->initController(service('request'), service('response'), service('logger'));
$this->setProtectedOrPrivateProperty($this->controller, 'customerModel', $this->customerModel);
$result = $this->controller->delete(999);
$output = json_decode($result->getJSON(), true);
$this->assertEquals(404, $result->getStatusCode());
$this->assertEquals('error', $output['status']);
$this->assertEquals('Customer with ID 999 not found.', $output['message']);
$this->assertNull($output['data']);
}
public function testDeleteCustomerSuccess()
{
$mockCustomer = ['id' => 1, 'name' => 'Test User'];
$this->customerModel = $this->getMockBuilder(Customer::class)
->onlyMethods(['find', 'delete'])
->getMock();
$this->customerModel->method('find')->with(1)->willReturn($mockCustomer);
$this->customerModel->method('delete')->with(1)->willReturn(true);
$this->controller = new CustomerController();
$this->controller->initController(service('request'), service('response'), service('logger'));
$this->setProtectedOrPrivateProperty($this->controller, 'customerModel', $this->customerModel);
$result = $this->controller->delete(1);
$output = json_decode($result->getJSON(), true);
$this->assertEquals(200, $result->getStatusCode());
$this->assertEquals('success', $output['status']);
$this->assertEquals('Customer with ID 1 deleted successfully.', $output['message']);
$this->assertNull($output['data']);
}
public function testUpdateFailsOnValidationError()
{
$id = 1;
$mockCustomerModel = $this->createMock(\App\Models\Customer::class);
$mockCustomerModel->method('find')->willReturn([
'id' => $id,
'phone' => '0123456789',
'email' => 'test@example.com'
]);
$this->setProtectedOrPrivateProperty($this->controller, 'customerModel', $mockCustomerModel);
$this->request->setGlobal('post', [
'customer_type' => 'Regular',
'customer_tier' => 'Bronze',
'birthday' => '1990-01-01',
'phone' => '0123456789',
'email' => 'test@example.com',
'name' => '', // Empty name = fail
]);
$this->setProtectedOrPrivateProperty($this->controller, 'request', $this->request);
$response = $this->controller->update($id);
$this->assertEquals(422, $response->getStatusCode());
$this->assertStringContainsString('Validation failed', $response->getBody());
}
public function testUpdateCustomerSuccess()
{
// Mock customer ID
$customerId = 1;
// Expected customer data returned by find()
$existingCustomer = [
'id' => $customerId,
'name' => 'Old Name',
'phone' => '0111111111',
'email' => 'old@example.com',
'customer_type' => 'Regular',
'membership_tier' => 'Silver',
'birthday' => '1990-01-01',
];
// Create mock for Customer model
$mockCustomerModel = $this->createMock(Customer::class);
$mockCustomerModel->method('find')->with($customerId)->willReturn($existingCustomer);
$mockCustomerModel->method('update')->willReturn(true);
// Mock SettingCustomerType model
$mockCustomerTypeModel = $this->getMockBuilder(SettingCustomerType::class)
->onlyMethods(['first']) // first() exists — use onlyMethods()
->addMethods(['where']) // where() is dynamic — use addMethods()
->getMock();
$mockCustomerTypeModel->method('where')->willReturnSelf();
$mockCustomerTypeModel->method('first')->willReturn(['name' => 'Regular']);
// Mock SettingMembershipTier model
$mockTierModel = $this->getMockBuilder(SettingMembershipTier::class)
->onlyMethods(['first']) // first() exists — use onlyMethods()
->addMethods(['where']) // where() is dynamic — use addMethods()
->getMock();
$mockTierModel->method('where')->willReturnSelf();
$mockTierModel->method('first')->willReturn([
'id' => 1,
'name' => 'Gold'
]);
// Mock the IncomingRequest
$mockRequest = $this->getMockBuilder(IncomingRequest::class)
->disableOriginalConstructor()
->onlyMethods(['getVar', 'getMethod'])
->getMock();
$mockRequest->method('getVar')->willReturnCallback(function ($key) {
$data = [
'id' => 1,
'phone' => '0123456789',
'name' => 'Updated Name',
'email' => 'updated@example.com',
'customer_type' => 'Regular',
'membership_tier' => 'Gold',
'birthday' => '1990-01-01',
];
return $data[$key] ?? null;
});
$mockRequest->method('getMethod')->willReturn('post');
// Mock validation to return true (skip validation errors)
$mockValidation = $this->getMockBuilder(\CodeIgniter\Validation\Validation::class)
->disableOriginalConstructor()
->onlyMethods(['run', 'getErrors'])
->getMock();
$mockValidation->method('run')->willReturn(true);
$mockValidation->method('getErrors')->willReturn([]);
\Config\Services::injectMock('validation', $mockValidation);
// Create controller and inject mocks
$controller = new CustomerController();
$this->setProtectedOrPrivateProperty($controller, 'request', $mockRequest);
$this->setProtectedOrPrivateProperty($controller, 'customerModel', $mockCustomerModel);
$this->setProtectedOrPrivateProperty($controller, 'customerTypeModel', $mockCustomerTypeModel);
$this->setProtectedOrPrivateProperty($controller, 'membershipTierModel', $mockTierModel);
$this->setProtectedOrPrivateProperty($controller, 'response', \Config\Services::response());
// Run update
$response = $controller->update($customerId);
// $statusCode = $response->getStatusCode();
// $body = $response->getBody();
// var_dump($statusCode);
// var_dump($body);
// Assert success
$this->assertSame(200, $response->getStatusCode());
}
public function testUpdateFailsWhenCustomerNotFound()
{
$mockCustomerModel = $this->createMock(\App\Models\Customer::class);
$mockCustomerModel->method('find')->willReturn(null);
$this->setProtectedOrPrivateProperty($this->controller, 'customerModel', $mockCustomerModel);
$response = $this->controller->update(999); // Non-existent ID
$this->assertEquals(404, $response->getStatusCode());
$this->assertStringContainsString('Customer with ID 999 not found', $response->getBody());
}
public function testUpdateFailsIfTierNotFound()
{
$id = 1;
// Mock customerModel
$mockCustomerModel = $this->createMock(\App\Models\Customer::class);
$mockCustomerModel->method('find')->willReturn([
'id' => $id,
'phone' => '0123456789',
'email' => 'test@example.com',
]);
// Mock membershipTierModel to return null (tier not found)
$mockTierModel = $this->getMockBuilder(SettingMembershipTier::class)
->onlyMethods(['first'])
->addMethods(['where'])
->getMock();
$mockTierModel->method('where')->willReturnSelf();
$mockTierModel->method('first')->willReturn(null);
// Set mocked models into the controller
$this->setProtectedOrPrivateProperty($this->controller, 'customerModel', $mockCustomerModel);
$this->setProtectedOrPrivateProperty($this->controller, 'membershipTierModel', $mockTierModel);
// Create and mock a request with POST data
$mockRequest = $this->getMockBuilder(IncomingRequest::class)
->disableOriginalConstructor()
->onlyMethods(['getVar', 'getMethod'])
->getMock();
$mockRequest->method('getVar')->willReturnMap([
['customer_type', null, 'Regular'],
['customer_tier', null, 'NonexistentTier'],
['birthday', null, '1990-01-01'],
['phone', null, '0123456789'],
['email', null, 'test@example.com'],
['name', null, 'John Doe'],
]);
$mockRequest->method('getMethod')->willReturn('post');
// Set the mocked request into the controller
$this->setProtectedOrPrivateProperty($this->controller, 'request', $mockRequest);
// Mock validation to return true (skip validation errors)
$mockValidation = $this->getMockBuilder(\CodeIgniter\Validation\Validation::class)
->disableOriginalConstructor()
->onlyMethods(['run', 'getErrors'])
->getMock();
$mockValidation->method('run')->willReturn(true);
$mockValidation->method('getErrors')->willReturn([]);
\Config\Services::injectMock('validation', $mockValidation);
// Call the update method
$response = $this->controller->update($id);
// Assertions
$this->assertEquals(404, $response->getStatusCode());
}
}