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

445 lines
19 KiB
PHP

<?php
namespace App\Tests\Controllers\Backend;
use App\Controllers\Backend\OutletController;
use App\Models\Outlet;
use App\Models\OutletTax;
use App\Models\OutletImage;
use App\Models\OutletOperatingDay;
use App\Models\OutletOperatingHour;
use App\Models\OutletOperatingHoursException;
use App\Models\SettingTax;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\HTTP\IncomingRequest;
use CodeIgniter\Database\ConnectionInterface;
use CodeIgniter\Database\QueryBuilder;
use Mockery;
use Config\Database as CI4Database;
class OutletTest extends CIUnitTestCase
{
protected $mockOutlet;
protected $mockOutletTax;
protected $mockOutletImage;
protected $mockOutletOperatingDay;
protected $mockOutletOperatingHour;
protected $mockOutletOperatingHoursException;
protected $mockSettingTax;
protected $controller;
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);
}
protected function tearDown(): void
{
Mockery::close();
parent::tearDown();
}
protected function setUp(): void
{
parent::setUp();
$this->mockOutlet = $this->createMock(Outlet::class);
$this->mockOutletTax = $this->getMockBuilder(OutletTax::class)
->addMethods(['where'])
->getMock();
$this->mockOutletImage = $this->createMock(OutletImage::class);
$this->mockOutletOperatingDay = $this->getMockBuilder(OutletOperatingDay::class)
->addMethods(['where'])
->getMock();
$this->mockOutletOperatingHour = $this->getMockBuilder(OutletOperatingHour::class)
->addMethods(['where'])
->getMock();
$this->mockOutletOperatingHoursException = $this->getMockBuilder(OutletOperatingHoursException::class)
->addMethods(['where'])
->getMock();
$this->mockSettingTax = $this->createMock(SettingTax::class);
$this->controller = new OutletController();
$this->setProtectedOrPrivateProperty($this->controller, 'outlet', $this->mockOutlet);
$this->setProtectedOrPrivateProperty($this->controller, 'outletTax', $this->mockOutletTax);
$this->setProtectedOrPrivateProperty($this->controller, 'outletImage', $this->mockOutletImage);
$this->setProtectedOrPrivateProperty($this->controller, 'outletOperatingDays', $this->mockOutletOperatingDay);
$this->setProtectedOrPrivateProperty($this->controller, 'outletOperatingHours', $this->mockOutletOperatingHour);
$this->setProtectedOrPrivateProperty($this->controller, 'outletOperatingHoursExceptions', $this->mockOutletOperatingHoursException);
$this->setProtectedOrPrivateProperty($this->controller, 'settingTax', $this->mockSettingTax);
$this->setProtectedOrPrivateProperty($this->controller, 'request', $this->createMock(IncomingRequest::class));
$this->setProtectedOrPrivateProperty($this->controller, 'response', service('response'));
}
public function testIndexReturnsOutlets()
{
$outlets = [
['id' => 1, 'title' => 'Outlet 1'],
['id' => 2, 'title' => 'Outlet 2'],
];
$this->mockOutlet->method('getOperatingHoursWithDaysList')->willReturn($outlets);
$result = $this->controller->index();
$this->assertEquals(200, $result->getStatusCode());
$data = json_decode($result->getBody(), true);
$this->assertIsArray($data);
$this->assertEquals(200, $data['status']);
$this->assertEquals($outlets, $data['result']);
}
public function testIndexReturnsNoOutlets()
{
$this->mockOutlet->method('getOperatingHoursWithDaysList')->willReturn([]);
$result = $this->controller->index();
$this->assertEquals(200, $result->getStatusCode());
$data = json_decode($result->getBody(), true);
$this->assertIsArray($data);
$this->assertEquals(400, $data['status']);
$this->assertEquals('No outlet data found.', $data['result']);
}
public function testShowReturnsOutlet()
{
$outlet = ['id' => 1, 'title' => 'Outlet 1'];
$this->mockOutlet->method('getOperatingHoursWithDays')->with(1)->willReturn($outlet);
$result = $this->controller->show(1);
$this->assertEquals(200, $result->getStatusCode());
$data = json_decode($result->getBody(), true);
$this->assertIsArray($data);
$this->assertEquals(200, $data['status']);
$this->assertEquals($outlet, $data['result']);
}
public function testShowReturnsNotFound()
{
$this->mockOutlet->method('getOperatingHoursWithDays')->with(99)->willReturn(null);
$result = $this->controller->show(99);
$this->assertEquals(200, $result->getStatusCode());
$data = json_decode($result->getBody(), true);
$this->assertIsArray($data);
$this->assertEquals(400, $data['status']);
$this->assertEquals('No outlet data found.', $data['result']);
}
public function testDeleteOutletNotFound()
{
$this->mockOutlet->method('find')->with(99)->willReturn(null);
$result = $this->controller->delete(99);
$this->assertEquals(200, $result->getStatusCode());
$data = json_decode($result->getBody(), true);
$this->assertIsArray($data);
$this->assertEquals(400, $data['status']);
$this->assertEquals('No outlet data found.', $data['result']);
}
public function testDeleteSuccess()
{
// Create mock builder objects for chainable methods
$mockTaxBuilder = $this->getMockBuilder(\CodeIgniter\Database\BaseBuilder::class)
->disableOriginalConstructor()
->onlyMethods(['delete'])
->getMock();
$mockDayBuilder = $this->getMockBuilder(\CodeIgniter\Database\BaseBuilder::class)
->disableOriginalConstructor()
->onlyMethods(['delete'])
->getMock();
$mockHourBuilder = $this->getMockBuilder(\CodeIgniter\Database\BaseBuilder::class)
->disableOriginalConstructor()
->onlyMethods(['delete'])
->getMock();
$mockExceptionBuilder = $this->getMockBuilder(\CodeIgniter\Database\BaseBuilder::class)
->disableOriginalConstructor()
->onlyMethods(['delete'])
->getMock();
$this->mockOutlet->method('find')->with(1)->willReturn(['id' => 1]);
$this->mockOutlet->expects($this->once())->method('delete')->with(1);
// Setup chainable method expectations
$this->mockOutletTax->method('where')->with('outlet_id', 1)->willReturn($mockTaxBuilder);
$mockTaxBuilder->expects($this->once())->method('delete')->willReturn(true);
$this->mockOutletOperatingDay->method('where')->with('outlet_id', 1)->willReturn($mockDayBuilder);
$mockDayBuilder->expects($this->once())->method('delete')->willReturn(true);
$this->mockOutletOperatingHour->method('where')->with('outlet_id', 1)->willReturn($mockHourBuilder);
$mockHourBuilder->expects($this->once())->method('delete')->willReturn(true);
$this->mockOutletOperatingHoursException->method('where')->with('outlet_id', 1)->willReturn($mockExceptionBuilder);
$mockExceptionBuilder->expects($this->once())->method('delete')->willReturn(true);
$result = $this->controller->delete(1);
$this->assertEquals(200, $result->getStatusCode());
$data = json_decode($result->getBody(), true);
$this->assertIsArray($data);
$this->assertEquals(200, $data['status']);
$this->assertEquals('Outlet deleted successfully.', $data['result']);
}
public function testUpdatePasswordOutletNotFound()
{
$this->mockOutlet->method('find')->with(99)->willReturn(null);
$result = $this->controller->updatePassword(99);
$this->assertEquals(200, $result->getStatusCode());
$data = json_decode($result->getBody(), true);
$this->assertIsArray($data);
$this->assertEquals(400, $data['status']);
$this->assertEquals('No outlet data found.', $data['result']);
}
public function testCreateValidationFails()
{
$controller = $this->getMockBuilder(OutletController::class)
->onlyMethods(['validate'])
->enableOriginalConstructor()
->getMock();
$controller->method('validate')->willReturn(false);
$validator = $this->getMockBuilder(\CodeIgniter\Validation\Validation::class)
->disableOriginalConstructor()
->getMock();
$validator->method('getErrors')->willReturn(['title' => 'Required']);
$this->setProtectedOrPrivateProperty($controller, 'validator', $validator);
$this->setProtectedOrPrivateProperty($controller, 'outlet', $this->mockOutlet);
$this->setProtectedOrPrivateProperty($controller, 'outletTax', $this->mockOutletTax);
$this->setProtectedOrPrivateProperty($controller, 'outletImage', $this->mockOutletImage);
$this->setProtectedOrPrivateProperty($controller, 'outletOperatingDays', $this->mockOutletOperatingDay);
$this->setProtectedOrPrivateProperty($controller, 'outletOperatingHours', $this->mockOutletOperatingHour);
$this->setProtectedOrPrivateProperty($controller, 'outletOperatingHoursExceptions', $this->mockOutletOperatingHoursException);
$this->setProtectedOrPrivateProperty($controller, 'settingTax', $this->mockSettingTax);
$this->setProtectedOrPrivateProperty($controller, 'request', $this->createMock(IncomingRequest::class));
$this->setProtectedOrPrivateProperty($controller, 'response', service('response'));
$result = $controller->create();
$this->assertEquals(400, $result->getStatusCode()); // 400 is CodeIgniter's default
$data = json_decode($result->getBody(), true);
$this->assertIsArray($data);
$this->assertArrayHasKey('messages', $data);
$this->assertArrayHasKey('title', $data['messages']);
}
public function testCreateSuccess()
{
// 1. Mock IncomingRequest
$request = $this->createMock(\CodeIgniter\HTTP\IncomingRequest::class);
$request->method('getVar')->willReturnMap([
['title', 'Outlet 1'],
['email', 'outlet1@example.com'],
['phone', '123456789'],
['address', '123 Street'],
['state', 'State'],
['postal_code', '12345'],
['country', 'Country'],
['latitude', '1.234'],
['longitude', '5.678'],
['password', 'password'],
['outlet_delivery_coverage', '10'],
['outlet_tax', json_encode([1, 2])],
['outlet_operating_days', json_encode([['0' => ['is_operated' => 1]]])],
['outlet_operating_hours', json_encode([['0' => [['start_time' => '09:00', 'end_time' => '18:00']]]])],
['outlet_operating_hours_exceptions', json_encode([])],
]);
$request->method('getFiles')->willReturn([]);
// 2. Mock Controller with validate() overridden
$controller = $this->getMockBuilder(\App\Controllers\Backend\OutletController::class)
->onlyMethods(['validate'])
->getMock();
$controller->method('validate')->willReturn(true);
// 3. Mock all Models and insert returns
$mockOutlet = $this->createMock(\App\Models\Outlet::class);
$mockOutlet->method('insert')->willReturn(1);
$mockOutlet->method('find')->willReturn(['id' => 1, 'title' => 'Outlet 1']);
$mockOutletTax = $this->createMock(\App\Models\OutletTax::class);
$mockOutletTax->method('insert')->willReturn(true);
$mockOutletOperatingDay = $this->createMock(\App\Models\OutletOperatingDay::class);
$mockOutletOperatingDay->method('insert')->willReturn(true);
$mockOutletOperatingHour = $this->createMock(\App\Models\OutletOperatingHour::class);
$mockOutletOperatingHour->method('insert')->willReturn(true);
$mockOutletOperatingHoursException = $this->createMock(\App\Models\OutletOperatingHoursException::class);
$mockOutletOperatingHoursException->method('insert')->willReturn(true);
$mockOutletImage = $this->createMock(\App\Models\OutletImage::class);
$mockSettingTax = $this->createMock(\App\Models\SettingTax::class);
// 4. Mock DB transaction flow
// Mock global DB connection
$mockDB = Mockery::mock(\CodeIgniter\Database\BaseConnection::class);
$mockDB->shouldReceive('transBegin')->andReturn(true);
$mockDB->shouldReceive('transCommit')->andReturn(true);
$mockDB->shouldReceive('transRollback')->andReturn(true);
$this->setProtectedOrPrivateProperty($controller, 'db', $mockDB);
// 5. Inject all mocked dependencies
$this->setProtectedOrPrivateProperty($controller, 'request', $request);
$this->setProtectedOrPrivateProperty($controller, 'outlet', $mockOutlet);
$this->setProtectedOrPrivateProperty($controller, 'outletTax', $mockOutletTax);
$this->setProtectedOrPrivateProperty($controller, 'outletImage', $mockOutletImage);
$this->setProtectedOrPrivateProperty($controller, 'outletOperatingDays', $mockOutletOperatingDay);
$this->setProtectedOrPrivateProperty($controller, 'outletOperatingHours', $mockOutletOperatingHour);
$this->setProtectedOrPrivateProperty($controller, 'outletOperatingHoursExceptions', $mockOutletOperatingHoursException);
$this->setProtectedOrPrivateProperty($controller, 'settingTax', $mockSettingTax);
$this->setProtectedOrPrivateProperty($controller, 'response', service('response'));
$this->setProtectedOrPrivateProperty($controller, 'db', $mockDB);
// 6. Call the method
$response = $controller->create();
// 7. Assert response
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode($response->getBody(), true);
$this->assertEquals('success', $data['status']);
$this->assertArrayHasKey('result', $data);
$this->assertEquals('Outlet 1', $data['result']['title']);
}
public function testUpdateValidationFails()
{
// Create controller mock that allows original validateData method to be called
$controller = $this->getMockBuilder(OutletController::class)
->onlyMethods([]) // Don't mock any methods - we want real validateData behavior
->enableOriginalConstructor()
->getMock();
// Create a proper validator mock
$validator = $this->getMockBuilder(\CodeIgniter\Validation\Validation::class)
->disableOriginalConstructor()
->getMock();
$validator->method('getErrors')->willReturn([
'title' => 'The title field is required.',
'email' => 'The email field is required.',
'phone' => 'The phone field is required.',
'address' => 'The address field is required.',
'state' => 'The state field is required.',
'postal_code' => 'The postal_code field is required.',
'country' => 'The country field is required.',
'latitude' => 'The latitude field is required.',
'longitude' => 'The longitude field is required.',
'outlet_delivery_coverage' => 'The outlet_delivery_coverage field is required.',
'outlet_tax' => 'The outlet_tax field is required.',
'outlet_operating_days' => 'The outlet_operating_days field is required.',
'outlet_operating_hours' => 'The outlet_operating_hours field is required.',
]);
// Create request mock with invalid data
$mockRequest = $this->getMockBuilder(IncomingRequest::class)
->disableOriginalConstructor()
->getMock();
$invalidData = [
'title' => '',
'email' => 'invalid-email',
'phone' => '3o4i234234',
'address' => 'dfdgdgdgerfsd',
'state' => 'Fkdfnskf',
'postal_code' => 'fsdf',
'country' => 'fsdfsd',
'latitude' => 'not-number',
'longitude' => 'not-number',
'outlet_delivery_coverage' => 'not-number',
'outlet_tax' => 'sdfsdfs',
'outlet_operating_days' => 'sfsfsd',
'outlet_operating_hours' => 'dsfsdfds',
'outlet_operating_hours_exceptions' => null,
'outlet_images' => null
];
// Fix: getVar() must return full array when called with no argument
$mockRequest->method('getVar')
->willReturnCallback(function ($key = null) use ($invalidData) {
if ($key === null) {
return $invalidData; // full array for validateData()
}
return $invalidData[$key] ?? null; // or specific key
});
// getJSON fallback (optional - may not be used in controller)
$mockRequest->method('getJSON')
->willReturn($invalidData);
// Inject our mocks
$this->setProtectedOrPrivateProperty($controller, 'validator', $validator);
$this->setProtectedOrPrivateProperty($controller, 'outlet', $this->mockOutlet);
$this->setProtectedOrPrivateProperty($controller, 'outletTax', $this->mockOutletTax);
$this->setProtectedOrPrivateProperty($controller, 'outletImage', $this->mockOutletImage);
$this->setProtectedOrPrivateProperty($controller, 'outletOperatingDays', $this->mockOutletOperatingDay);
$this->setProtectedOrPrivateProperty($controller, 'outletOperatingHours', $this->mockOutletOperatingHour);
$this->setProtectedOrPrivateProperty($controller, 'outletOperatingHoursExceptions', $this->mockOutletOperatingHoursException);
$this->setProtectedOrPrivateProperty($controller, 'settingTax', $this->mockSettingTax);
$this->setProtectedOrPrivateProperty($controller, 'request', $mockRequest);
$this->setProtectedOrPrivateProperty($controller, 'response', service('response'));
// Mock outlet exists
$this->mockOutlet->method('find')->with(1)->willReturn(['id' => 1]);
// Call the update method
$result = $controller->update(1);
// Assertions
$this->assertEquals(400, $result->getStatusCode());
$data = json_decode($result->getBody(), true);
$this->assertIsArray($data);
$this->assertArrayHasKey('status', $data);
$this->assertEquals('400', $data['status']);
// $this->assertArrayHasKey('message', $data);
// $this->assertEquals('The title field is required.', $data['message']);
}
public function testUpdateOutletNotFound()
{
$this->mockOutlet->method('find')->with(99)->willReturn(null);
$result = $this->controller->update(99);
$this->assertEquals(200, $result->getStatusCode());
$data = json_decode($result->getBody(), true);
$this->assertIsArray($data);
$this->assertEquals(400, $data['status']);
$this->assertEquals('No outlet data found.', $data['result']);
}
}