initial project

This commit is contained in:
2025-11-06 13:41:06 +08:00
commit 1b08727cb2
209 changed files with 31602 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
<IfModule authz_core_module>
Require all denied
</IfModule>
<IfModule !authz_core_module>
Deny from all
</IfModule>
+118
View File
@@ -0,0 +1,118 @@
# Running Application Tests
This is the quick-start to CodeIgniter testing. Its intent is to describe what
it takes to set up your application and get it ready to run unit tests.
It is not intended to be a full description of the test features that you can
use to test your application. Those details can be found in the documentation.
## Resources
* [CodeIgniter 4 User Guide on Testing](https://codeigniter.com/user_guide/testing/index.html)
* [PHPUnit docs](https://phpunit.de/documentation.html)
* [Any tutorials on Unit testing in CI4?](https://forum.codeigniter.com/showthread.php?tid=81830)
## Requirements
It is recommended to use the latest version of PHPUnit. At the time of this
writing, we are running version 9.x. Support for this has been built into the
**composer.json** file that ships with CodeIgniter and can easily be installed
via [Composer](https://getcomposer.org/) if you don't already have it installed globally.
```console
> composer install
```
If running under macOS or Linux, you can create a symbolic link to make running tests a touch nicer.
```console
> ln -s ./vendor/bin/phpunit ./phpunit
```
You also need to install [XDebug](https://xdebug.org/docs/install) in order
for code coverage to be calculated successfully. After installing `XDebug`, you must add `xdebug.mode=coverage` in the **php.ini** file to enable code coverage.
## Setting Up
A number of the tests use a running database.
In order to set up the database edit the details for the `tests` group in
**app/Config/Database.php** or **.env**.
Make sure that you provide a database engine that is currently running on your machine.
More details on a test database setup are in the
[Testing Your Database](https://codeigniter.com/user_guide/testing/database.html) section of the documentation.
## Running the tests
The entire test suite can be run by simply typing one command-line command from the main directory.
```console
> ./phpunit
```
If you are using Windows, use the following command.
```console
> vendor\bin\phpunit
```
You can limit tests to those within a single test directory by specifying the
directory name after phpunit.
```console
> ./phpunit app/Models
```
## Generating Code Coverage
To generate coverage information, including HTML reports you can view in your browser,
you can use the following command:
```console
> ./phpunit --colors --coverage-text=tests/coverage.txt --coverage-html=tests/coverage/ -d memory_limit=1024m
```
This runs all of the tests again collecting information about how many lines,
functions, and files are tested. It also reports the percentage of the code that is covered by tests.
It is collected in two formats: a simple text file that provides an overview as well
as a comprehensive collection of HTML files that show the status of every line of code in the project.
The text file can be found at **tests/coverage.txt**.
The HTML files can be viewed by opening **tests/coverage/index.html** in your favorite browser.
## PHPUnit XML Configuration
The repository has a ``phpunit.xml.dist`` file in the project root that's used for
PHPUnit configuration. This is used to provide a default configuration if you
do not have your own configuration file in the project root.
The normal practice would be to copy ``phpunit.xml.dist`` to ``phpunit.xml``
(which is git ignored), and to tailor it as you see fit.
For instance, you might wish to exclude database tests, or automatically generate
HTML code coverage reports.
## Test Cases
Every test needs a *test case*, or class that your tests extend. CodeIgniter 4
provides one class that you may use directly:
* `CodeIgniter\Test\CIUnitTestCase`
Most of the time you will want to write your own test cases that extend `CIUnitTestCase`
to hold functions and services common to your test suites.
## Creating Tests
All tests go in the **tests/** directory. Each test file is a class that extends a
**Test Case** (see above) and contains methods for the individual tests. These method
names must start with the word "test" and should have descriptive names for precisely what
they are testing:
`testUserCanModifyFile()` `testOutputColorMatchesInput()` `testIsLoggedInFailsWithInvalidUser()`
Writing tests is an art, and there are many resources available to help learn how.
Review the links above and always pay attention to your code coverage.
### Database Tests
Tests can include migrating, seeding, and testing against a mock or live database.
Be sure to modify the test case (or create your own) to point to your seed and migrations
and include any additional steps to be run before tests in the `setUp()` method.
See [Testing Your Database](https://codeigniter.com/user_guide/testing/database.html)
for details.
@@ -0,0 +1,37 @@
<?php
namespace Tests\Support\Database\Migrations;
use CodeIgniter\Database\Migration;
class ExampleMigration extends Migration
{
protected $DBGroup = 'tests';
public function up(): void
{
$this->forge->addField('id');
$this->forge->addField([
'name' => ['type' => 'varchar', 'constraint' => 31],
'uid' => ['type' => 'varchar', 'constraint' => 31],
'class' => ['type' => 'varchar', 'constraint' => 63],
'icon' => ['type' => 'varchar', 'constraint' => 31],
'summary' => ['type' => 'varchar', 'constraint' => 255],
'created_at' => ['type' => 'datetime', 'null' => true],
'updated_at' => ['type' => 'datetime', 'null' => true],
'deleted_at' => ['type' => 'datetime', 'null' => true],
]);
$this->forge->addKey('name');
$this->forge->addKey('uid');
$this->forge->addKey(['deleted_at', 'id']);
$this->forge->addKey('created_at');
$this->forge->createTable('factories');
}
public function down(): void
{
$this->forge->dropTable('factories');
}
}
@@ -0,0 +1,41 @@
<?php
namespace Tests\Support\Database\Seeds;
use CodeIgniter\Database\Seeder;
class ExampleSeeder extends Seeder
{
public function run(): void
{
$factories = [
[
'name' => 'Test Factory',
'uid' => 'test001',
'class' => 'Factories\Tests\NewFactory',
'icon' => 'fas fa-puzzle-piece',
'summary' => 'Longer sample text for testing',
],
[
'name' => 'Widget Factory',
'uid' => 'widget',
'class' => 'Factories\Tests\WidgetPlant',
'icon' => 'fas fa-puzzle-piece',
'summary' => 'Create widgets in your factory',
],
[
'name' => 'Evil Factory',
'uid' => 'evil-maker',
'class' => 'Factories\Evil\MyFactory',
'icon' => 'fas fa-book-dead',
'summary' => 'Abandon all hope, ye who enter here',
],
];
$builder = $this->db->table('factories');
foreach ($factories as $factory) {
$builder->insert($factory);
}
}
}
+19
View File
@@ -0,0 +1,19 @@
<?php
namespace Tests\Support\Libraries;
use Config\App;
/**
* Class ConfigReader
*
* An extension of BaseConfig that prevents the constructor from
* loading external values. Used to read actual local values from
* a config file.
*/
class ConfigReader extends App
{
public function __construct()
{
}
}
+24
View File
@@ -0,0 +1,24 @@
<?php
namespace Tests\Support\Models;
use CodeIgniter\Model;
class ExampleModel extends Model
{
protected $table = 'factories';
protected $primaryKey = 'id';
protected $returnType = 'object';
protected $useSoftDeletes = false;
protected $allowedFields = [
'name',
'uid',
'class',
'icon',
'summary',
];
protected $useTimestamps = true;
protected $validationRules = [];
protected $validationMessages = [];
protected $skipValidation = false;
}
+46
View File
@@ -0,0 +1,46 @@
<?php
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\DatabaseTestTrait;
use Tests\Support\Database\Seeds\ExampleSeeder;
use Tests\Support\Models\ExampleModel;
/**
* @internal
*/
final class ExampleDatabaseTest extends CIUnitTestCase
{
use DatabaseTestTrait;
protected $seed = ExampleSeeder::class;
public function testModelFindAll(): void
{
$model = new ExampleModel();
// Get every row created by ExampleSeeder
$objects = $model->findAll();
// Make sure the count is as expected
$this->assertCount(3, $objects);
}
public function testSoftDeleteLeavesRow(): void
{
$model = new ExampleModel();
$this->setPrivateProperty($model, 'useSoftDeletes', true);
$this->setPrivateProperty($model, 'tempUseSoftDeletes', true);
/** @var stdClass $object */
$object = $model->first();
$model->delete($object->id);
// The model should no longer find it
$this->assertNull($model->find($object->id));
// ... but it should still be in the database
$result = $model->builder()->where('id', $object->id)->get()->getResult();
$this->assertCount(1, $result);
}
}
+11
View File
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<title>403 Forbidden</title>
</head>
<body>
<p>Directory access is forbidden.</p>
</body>
</html>
+17
View File
@@ -0,0 +1,17 @@
<?php
use CodeIgniter\Test\CIUnitTestCase;
/**
* @internal
*/
final class ExampleSessionTest extends CIUnitTestCase
{
public function testSessionSimple(): void
{
$session = service('session');
$session->set('logged_in', 123);
$this->assertSame(123, $session->get('logged_in'));
}
}
+105
View File
@@ -0,0 +1,105 @@
<?php
namespace Tests\Unit;
use CodeIgniter\Test\CIUnitTestCase;
use App\Services\CalculateService;
use App\Services\PromoService;
class CalculateServiceTest extends CIUnitTestCase
{
protected $calculateService;
protected function setUp(): void
{
parent::setUp();
// Mock PromoService for testing
/** @var PromoService $promoService */
$promoService = $this->createMock(PromoService::class);
$this->calculateService = new CalculateService($promoService);
}
public function testCalculateDeliveryFee()
{
// Test cases for delivery fee calculation
$testCases = [
['distance' => 0, 'expected' => 0.00],
['distance' => 2.5, 'expected' => 5.00], // Below 5 km: RM 5
['distance' => 5.0, 'expected' => 7.00], // 5-6.99 km: RM 7
['distance' => 6.99, 'expected' => 7.00], // 5-6.99 km: RM 7
['distance' => 7.0, 'expected' => 12.00], // 7-12.99 km: RM 12
['distance' => 10.5, 'expected' => 12.00], // 7-12.99 km: RM 12
['distance' => 12.99, 'expected' => 12.00], // 7-12.99 km: RM 12
['distance' => 13.0, 'expected' => 15.00], // 13-20 km: RM 15
['distance' => 16.5, 'expected' => 15.00], // 13-20 km: RM 15
['distance' => 20.0, 'expected' => 15.00], // 13-20 km: RM 15
['distance' => 22.0, 'expected' => 17.00], // Above 20 km: +RM 2 for every 5 km
['distance' => 25.0, 'expected' => 17.00], // Above 20 km: +RM 2 for every 5 km
['distance' => 27.0, 'expected' => 19.00], // Above 20 km: +RM 2 for every 5 km
['distance' => 30.0, 'expected' => 19.00], // Above 20 km: +RM 2 for every 5 km
];
foreach ($testCases as $testCase) {
$result = $this->calculateService->calculateDeliveryFee($testCase['distance']);
$this->assertEquals(
$testCase['expected'],
$result,
"Distance: {$testCase['distance']} km should cost RM {$testCase['expected']}, but got RM {$result}"
);
}
}
public function testCalculateDeliveryFeeWithInvalidInput()
{
// Test invalid inputs
$invalidInputs = [
-5.0, // Negative distance
'invalid', // String
null, // Null
[], // Array
true, // Boolean
];
foreach ($invalidInputs as $invalidInput) {
$result = $this->calculateService->calculateDeliveryFee($invalidInput);
$this->assertEquals(0, $result, "Invalid input '{$invalidInput}' should return 0");
}
}
public function testCalculateDistanceWithInvalidInput()
{
// Test invalid outlet ID
$result = $this->calculateService->calculateDistance('invalid', 3.123, 101.654);
$this->assertEquals(0, $result, 'Invalid outlet ID should return 0');
// Test invalid coordinates
$result = $this->calculateService->calculateDistance(1, 'invalid_lat', 101.654);
$this->assertEquals(0, $result, 'Invalid latitude should return 0');
$result = $this->calculateService->calculateDistance(1, 3.123, 'invalid_lng');
$this->assertEquals(0, $result, 'Invalid longitude should return 0');
// Test out of range coordinates
$result = $this->calculateService->calculateDistance(1, 100.0, 101.654); // Latitude > 90
$this->assertEquals(0, $result, 'Latitude > 90 should return 0');
$result = $this->calculateService->calculateDistance(1, 3.123, 200.0); // Longitude > 180
$this->assertEquals(0, $result, 'Longitude > 180 should return 0');
}
public function testDeliveryFeeCalculationLogic()
{
// Test edge cases around the boundaries
$this->assertEquals(5.00, $this->calculateService->calculateDeliveryFee(4.99), '4.99 km should be RM 5');
$this->assertEquals(7.00, $this->calculateService->calculateDeliveryFee(5.00), '5.00 km should be RM 7');
$this->assertEquals(7.00, $this->calculateService->calculateDeliveryFee(6.99), '6.99 km should be RM 7');
$this->assertEquals(12.00, $this->calculateService->calculateDeliveryFee(7.00), '7.00 km should be RM 12');
$this->assertEquals(12.00, $this->calculateService->calculateDeliveryFee(12.99), '12.99 km should be RM 12');
$this->assertEquals(15.00, $this->calculateService->calculateDeliveryFee(13.00), '13.00 km should be RM 15');
$this->assertEquals(15.00, $this->calculateService->calculateDeliveryFee(20.00), '20.00 km should be RM 15');
$this->assertEquals(17.00, $this->calculateService->calculateDeliveryFee(21.00), '21.00 km should be RM 17');
$this->assertEquals(17.00, $this->calculateService->calculateDeliveryFee(24.99), '24.99 km should be RM 17');
$this->assertEquals(19.00, $this->calculateService->calculateDeliveryFee(25.00), '25.00 km should be RM 19');
}
}
+832
View File
@@ -0,0 +1,832 @@
<?php
use CodeIgniter\Test\CIUnitTestCase;
use App\Controllers\CustomerController;
use App\Models\Customer;
use App\Models\CustomerAddress;
use CodeIgniter\HTTP\IncomingRequest;
// use App\Controllers\Backend\CustomerController;
class CustomerAddressTest extends CIUnitTestCase
{
protected $controller;
protected $request;
protected $customerModelMock;
protected $customerAddressModelMock;
protected function setUp(): void
{
parent::setUp();
$this->controller = new \App\Controllers\Backend\CustomerController();
$this->controller->initController(
service('request'),
service('response'),
service('logger')
);
$this->request = $this->createMock(IncomingRequest::class);
$this->setProtectedOrPrivateProperty($this->controller, 'request', $this->request);
// Create and inject model mocks
$this->customerModelMock = $this->createMock(\App\Models\Customer::class);
$this->customerAddressModelMock = $this->createMock(\App\Models\CustomerAddress::class);
\Config\Services::injectMock('customer', $this->customerModelMock);
\Config\Services::injectMock('customeraddress', $this->customerAddressModelMock);
}
protected function setProtectedOrPrivateProperty($object, $property, $value)
{
$reflection = new \ReflectionObject($object);
while (!$reflection->hasProperty($property)) {
$reflection = $reflection->getParentClass();
}
$prop = $reflection->getProperty($property);
$prop->setAccessible(true);
$prop->setValue($object, $value);
}
public function testCreateCustomerAddressReturnsIfCustomerIdMissing()
{
$response = $this->controller->createCustomerAddress();
$this->assertEquals(422, $response->getStatusCode());
$json = json_decode($response->getBody(), true);
$this->assertEquals('error', $json['status']);
$this->assertEquals('Customer ID is required.', $json['message']);
}
public function testCreateCustomerAddressReturnsIfCustomerNotFound()
{
$mockCustomerModel = $this->getMockBuilder(Customer::class)
->addMethods(['where'])
->onlyMethods(['first'])
->disableOriginalConstructor()
->getMock();
$mockCustomerModel->method('where')->willReturnSelf();
$mockCustomerModel->method('first')->willReturn(null);
$this->setProtectedOrPrivateProperty($this->controller, 'customerModel', $mockCustomerModel);
$response = $this->controller->createCustomerAddress(1);
$this->assertEquals(404, $response->getStatusCode());
$json = json_decode($response->getBody(), true);
$this->assertEquals('error', $json['status']);
$this->assertEquals('Customer with ID 1 not found or not active.', $json['message']);
}
public function testCreateCustomerAddressReturnsOnSuccess()
{
$mockCustomerModel = $this->getMockBuilder(Customer::class)
->addMethods(['where'])
->onlyMethods(['first'])
->disableOriginalConstructor()
->getMock();
$mockCustomerModel->method('where')->willReturnSelf();
$mockCustomerModel->method('first')->willReturn(['id' => 1, 'status' => 'active']);
$mockCustomerAddressModel = $this->getMockBuilder(CustomerAddress::class)
->onlyMethods(['set', 'update', 'insert'])
->addMethods(['where'])
->disableOriginalConstructor()
->getMock();
$mockCustomerAddressModel->method('where')->willReturnSelf();
$mockCustomerAddressModel->method('set')->willReturnSelf();
$mockCustomerAddressModel->method('update')->willReturn(true);
$mockCustomerAddressModel->method('insert')->willReturn(true);
$mockRequest = $this->createMock(IncomingRequest::class);
$mockRequest->method('getVar')->willReturnMap([
['name', null, 'John'],
['phone', null, '123456789'],
['address', null, '123 Main St'],
['unit', null, 'Unit 5'],
['note', null, 'Test note'],
]);
$this->setProtectedOrPrivateProperty($this->controller, 'request', $mockRequest);
$this->setProtectedOrPrivateProperty($this->controller, 'customerModel', $mockCustomerModel);
$this->setProtectedOrPrivateProperty($this->controller, 'customerAddressModel', $mockCustomerAddressModel);
$response = $this->controller->createCustomerAddress(1);
$this->assertEquals(201, $response->getStatusCode());
$json = json_decode($response->getBody(), true);
$this->assertEquals('success', $json['status']);
$this->assertEquals('Customer address created successfully.', $json['message']);
}
public function testCreateCustomerAddressReturnsOnInsertFailure()
{
$mockCustomerModel = $this->getMockBuilder(Customer::class)
->addMethods(['where'])
->onlyMethods(['first'])
->disableOriginalConstructor()
->getMock();
$mockCustomerModel->method('where')->willReturnSelf();
$mockCustomerModel->method('first')->willReturn(['id' => 1, 'status' => 'active']);
$mockCustomerAddressModel = $this->getMockBuilder(CustomerAddress::class)
->onlyMethods(['set', 'update', 'insert'])
->addMethods(['where'])
->disableOriginalConstructor()
->getMock();
$mockCustomerAddressModel->method('where')->willReturnSelf();
$mockCustomerAddressModel->method('set')->willReturnSelf();
$mockCustomerAddressModel->method('update')->willReturn(true);
$mockCustomerAddressModel->method('insert')->willReturn(false);
$mockRequest = $this->createMock(IncomingRequest::class);
$mockRequest->method('getVar')->willReturn('Some value');
$this->setProtectedOrPrivateProperty($this->controller, 'request', $mockRequest);
$this->setProtectedOrPrivateProperty($this->controller, 'customerModel', $mockCustomerModel);
$this->setProtectedOrPrivateProperty($this->controller, 'customerAddressModel', $mockCustomerAddressModel);
$response = $this->controller->createCustomerAddress(1);
$this->assertEquals(500, $response->getStatusCode());
$json = json_decode($response->getBody(), true);
$this->assertEquals('error', $json['status']);
$this->assertEquals('Failed to create customer address.', $json['message']);
}
public function testCustomerAddressIndexReturnsMultipleAddresses()
{
// Arrange: Prepare a list of 2 fake addresses
$fakeAddresses = [
[
'id' => 1,
'customer_id' => 1,
'name' => 'Home',
'phone' => '0123456789',
'address' => '123 Test Street',
'unit' => 'A-1-1',
'note' => 'Ring bell twice',
'is_default' => 1,
'created_at' => '2025-07-30 10:00:00',
'updated_at' => '2025-07-30 10:00:00',
'deleted_at' => null,
],
[
'id' => 2,
'customer_id' => 1,
'name' => 'Office',
'phone' => '0987654321',
'address' => '456 Work Avenue',
'unit' => 'B-2-2',
'note' => 'Reception 3rd floor',
'is_default' => 0,
'created_at' => '2025-07-30 11:00:00',
'updated_at' => '2025-07-30 11:00:00',
'deleted_at' => null,
],
];
// Create a mock for CustomerAddress model
$mockCustomerAddressModel = $this->createMock(\App\Models\CustomerAddress::class);
$mockCustomerAddressModel
->expects($this->once())
->method('findAll')
->willReturn($fakeAddresses);
$this->setProtectedOrPrivateProperty($this->controller, 'customerAddressModel', $mockCustomerAddressModel);
$response = $this->controller->customerAddressIndex();
$this->assertIsObject($response);
$this->assertEquals(200, $response->getStatusCode());
$decoded = json_decode($response->getBody(), true);
$this->assertEquals('success', $decoded['status']);
$this->assertEquals('Customer addresses retrieved successfully.', $decoded['message']);
$this->assertCount(2, $decoded['data']); // Ensure two addresses
$this->assertEquals($fakeAddresses, $decoded['data']);
}
public function testShowCustomerAddressSuccess()
{
$addressData = [
'id' => 1,
'customer_id' => 101,
'name' => 'Home',
'phone' => '0123456789',
'address' => '123 Main St',
'unit' => 'A-1-1',
'note' => 'Main house',
'is_default' => 1,
];
$mockCustomerAddressModel = $this->createMock(CustomerAddress::class);
$mockCustomerAddressModel->method('find')->with(1)->willReturn($addressData);
$this->setProtectedOrPrivateProperty($this->controller, 'customerAddressModel', $mockCustomerAddressModel);
$response = $this->controller->showCustomerAddress(1);
$this->assertEquals(200, $response->getStatusCode());
$this->assertStringContainsString('Customer address retrieved successfully', $response->getBody());
}
public function testShowCustomerAddressNotFound()
{
$mockCustomerAddressModel = $this->createMock(CustomerAddress::class);
$mockCustomerAddressModel->method('find')->with(999)->willReturn(null);
$this->setProtectedOrPrivateProperty($this->controller, 'customerAddressModel', $mockCustomerAddressModel);
$response = $this->controller->showCustomerAddress(999);
$this->assertEquals(404, $response->getStatusCode());
$this->assertStringContainsString('not found', $response->getBody());
}
public function testShowCustomerAddressMissingId()
{
$this->controller = new \App\Controllers\Backend\CustomerController();
$this->setProtectedOrPrivateProperty($this->controller, 'response', \Config\Services::response());
$response = $this->controller->showCustomerAddress(null);
$this->assertEquals(422, $response->getStatusCode());
$this->assertStringContainsString('Address ID is required', $response->getBody());
}
public function testShowAddressesByCustomerSuccess()
{
$mockModel = $this->getMockBuilder(CustomerAddress::class)
->onlyMethods(['findAll'])
->addMethods(['where'])
->disableOriginalConstructor()
->getMock();
$mockModel->method('where')->with('customer_id', 101)->willReturnSelf();
$mockModel->method('findAll')->willReturn([
['id' => 1, 'customer_id' => 101, 'address' => '123 Main St'],
['id' => 2, 'customer_id' => 101, 'address' => '456 Side St']
]);
// $this->controller = new CustomerController();
$this->setProtectedOrPrivateProperty($this->controller, 'customerAddressModel', $mockModel);
$response = $this->controller->showAddressesByCustomer(101);
$this->assertEquals(200, $response->getStatusCode());
$this->assertStringContainsString('Customer addresses retrieved successfully', $response->getBody());
}
public function testShowAddressesByCustomerEmpty()
{
$mockModel = $this->getMockBuilder(CustomerAddress::class)
->onlyMethods(['findAll'])
->addMethods(['where'])
->disableOriginalConstructor()
->getMock();
$mockModel->method('where')->with('customer_id', 999)->willReturnSelf();
$mockModel->method('findAll')->willReturn([]);
// $this->controller = new CustomerController();
$this->setProtectedOrPrivateProperty($this->controller, 'customerAddressModel', $mockModel);
$response = $this->controller->showAddressesByCustomer(999);
$this->assertEquals(404, $response->getStatusCode());
$this->assertStringContainsString('No addresses found', $response->getBody());
}
public function testShowAddressesByCustomerMissingId()
{
$this->controller = new \App\Controllers\Backend\CustomerController();
$this->setProtectedOrPrivateProperty($this->controller, 'response', \Config\Services::response());
$response = $this->controller->showAddressesByCustomer(null);
$this->assertEquals(422, $response->getStatusCode());
$this->assertStringContainsString('Customer ID is required', $response->getBody());
}
public function testUpdateCustomerAddressMissingId()
{
$result = $this->controller->updateCustomerAddress(null);
$this->assertEquals(422, $result->getStatusCode());
$this->assertStringContainsString('Address ID is required', $result->getJSON());
}
public function testUpdateCustomerAddressInvalidId()
{
// Mock CustomerAddress model where 'find' returns null
$mockAddressModel = $this->createMock(CustomerAddress::class);
$mockAddressModel->method('find')->with(99)->willReturn(null);
$this->setProtectedOrPrivateProperty($this->controller, 'customerAddressModel', $mockAddressModel);
// Customer model might still be called depending on controller flow — mock it
$mockCustomerModel = $this->createMock(Customer::class);
$this->setProtectedOrPrivateProperty($this->controller, 'customerModel', $mockCustomerModel);
$result = $this->controller->updateCustomerAddress(99);
$this->assertEquals(404, $result->getStatusCode());
$this->assertStringContainsString('not found', $result->getBody());
}
public function testUpdateCustomerAddressCustomerNotFound()
{
$mockAddressModel = $this->getMockBuilder(CustomerAddress::class)
->onlyMethods(['find'])
->addMethods(['where'])
->disableOriginalConstructor()
->getMock();
$mockAddressModel->method('find')->willReturn(['customer_id' => 5]);
$mockCustomerModel = $this->getMockBuilder(Customer::class)
->addMethods(['where'])
->onlyMethods(['first'])
->disableOriginalConstructor()
->getMock();
$mockCustomerModel->method('where')->willReturnSelf();
$mockCustomerModel->method('first')->willReturn(null);
$this->setProtectedOrPrivateProperty($this->controller, 'customerModel', $mockCustomerModel);
$this->setProtectedOrPrivateProperty($this->controller, 'customerAddressModel', $mockAddressModel);
$result = $this->controller->updateCustomerAddress(1);
$this->assertEquals(404, $result->getStatusCode());
$this->assertStringContainsString('Customer not found', $result->getJSON());
}
public function testUpdateCustomerAddressValidationFail()
{
$mockAddressModel = $this->createMock(CustomerAddress::class);
$mockAddressModel->method('find')->willReturn(['customer_id' => 1]);
$mockCustomerModel = $this->getMockBuilder(Customer::class)
->addMethods(['where'])
->onlyMethods(['first'])
->disableOriginalConstructor()
->getMock();
$mockCustomerModel->method('where')->willReturnSelf();
$mockCustomerModel->method('first')->willReturn(['id' => 1, 'status' => 'active']);
// Inject into controller directly
$this->setProtectedOrPrivateProperty($this->controller, 'customerModel', $mockCustomerModel);
$this->setProtectedOrPrivateProperty($this->controller, 'customerAddressModel', $mockAddressModel);
// Mock request object
$request = \Config\Services::request();
$request->setGlobal('post', [
'name' => '', // validation fail
'phone' => '',
'address' => '',
'is_default' => ''
]);
$this->setProtectedOrPrivateProperty($this->controller, 'request', $request);
$result = $this->controller->updateCustomerAddress(1);
$this->assertEquals(422, $result->getStatusCode());
$this->assertStringContainsString('Validation failed', $result->getJSON());
}
public function testUpdateCustomerAddressSuccess()
{
$addressId = 1;
$existingAddress = [
'id' => $addressId,
'customer_id' => 1,
'name' => 'Old Address Name',
'phone' => '0123456789',
'address' => 'Old Street',
'unit' => 'B-2-3',
'note' => 'Old note',
'is_default' => 0,
];
$mockCustomerAddressModel = $this->getMockBuilder(\App\Models\CustomerAddress::class)
->onlyMethods(['find', 'update', 'set'])
->addMethods(['where'])
->disableOriginalConstructor()
->getMock();
$mockCustomerAddressModel->method('find')->with($addressId)->willReturn($existingAddress);
$mockCustomerAddressModel->method('update')->willReturn(true);
$mockCustomerAddressModel->method('where')->willReturnSelf(); // For setting others to is_default = 0
$mockCustomerAddressModel->method('set')->willReturnSelf();
// Mock Customer model
$mockCustomerModel = $this->getMockBuilder(\App\Models\Customer::class)
->onlyMethods(['first'])
->addMethods(['where'])
->disableOriginalConstructor()
->getMock();
$mockCustomerModel->method('where')->willReturnSelf();
$mockCustomerModel->method('first')->willReturn([
'id' => 1,
'name' => 'Test Customer',
'status' => 'active',
'deleted_at' => null,
]);
// Simulate POST data
$mockRequest = $this->getMockBuilder(IncomingRequest::class)
->disableOriginalConstructor()
->onlyMethods(['getVar', 'getMethod'])
->getMock();
$mockRequest->method('getVar')->willReturnCallback(function ($key = null) {
$data = [
'name' => 'Updated Address Name',
'phone' => '0199999999',
'address' => 'New Street',
'unit' => 'A-1-1',
'note' => 'New note',
'is_default' => 1,
'customer_id' => 1,
];
return $key ? ($data[$key] ?? null) : $data;
});
$mockRequest->method('getMethod')->willReturn('post');
// Validation mock
$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 dependencies
$controller = new \App\Controllers\Backend\CustomerController();
$this->setProtectedOrPrivateProperty($controller, 'request', $mockRequest);
$this->setProtectedOrPrivateProperty($controller, 'customerAddressModel', $mockCustomerAddressModel);
$this->setProtectedOrPrivateProperty($controller, 'customerModel', $mockCustomerModel);
$this->setProtectedOrPrivateProperty($controller, 'response', \Config\Services::response());
// Run the controller method
$response = $controller->updateCustomerAddress($addressId);
$statusCode = $response->getStatusCode();
$body = $response->getBody();
// var_dump($statusCode);
// var_dump($body);
// Assert the response
$this->assertSame(200, $response->getStatusCode());
$decodedBody = json_decode($response->getBody(), true);
$this->assertEquals('success', $decodedBody['status']);
$this->assertEquals('Customer address updated successfully.', $decodedBody['message']);
}
public function testUpdateCustomerAddressFailsToSave()
{
$addressId = 1;
$existingAddress = [
'id' => $addressId,
'customer_id' => 1,
'name' => 'Old Address Name',
'phone' => '0123456789',
'address' => 'Old Street',
'unit' => 'B-2-3',
'note' => 'Old note',
'is_default' => 0,
];
$mockCustomerAddressModel = $this->getMockBuilder(\App\Models\CustomerAddress::class)
->onlyMethods(['find', 'update', 'set'])
->addMethods(['where'])
->disableOriginalConstructor()
->getMock();
$mockCustomerAddressModel->method('find')->with($addressId)->willReturn($existingAddress);
$mockCustomerAddressModel->method('update')->willReturn(false); // Simulate update failure
$mockCustomerAddressModel->method('where')->willReturnSelf();
$mockCustomerAddressModel->method('set')->willReturnSelf();
$mockCustomerModel = $this->getMockBuilder(\App\Models\Customer::class)
->onlyMethods(['first'])
->addMethods(['where'])
->disableOriginalConstructor()
->getMock();
$mockCustomerModel->method('where')->willReturnSelf();
$mockCustomerModel->method('first')->willReturn([
'id' => 1,
'name' => 'Test Customer',
'status' => 'active',
'deleted_at' => null,
]);
$mockRequest = $this->getMockBuilder(IncomingRequest::class)
->disableOriginalConstructor()
->onlyMethods(['getVar', 'getMethod'])
->getMock();
$mockRequest->method('getVar')->willReturnCallback(function ($key = null) {
$data = [
'name' => 'Updated Address Name',
'phone' => '0199999999',
'address' => 'New Street',
'unit' => 'A-1-1',
'note' => 'New note',
'is_default' => 1,
'customer_id' => 1,
];
return $key ? ($data[$key] ?? null) : $data;
});
$mockRequest->method('getMethod')->willReturn('post');
$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);
$controller = new \App\Controllers\Backend\CustomerController();
$this->setProtectedOrPrivateProperty($controller, 'request', $mockRequest);
$this->setProtectedOrPrivateProperty($controller, 'customerAddressModel', $mockCustomerAddressModel);
$this->setProtectedOrPrivateProperty($controller, 'customerModel', $mockCustomerModel);
$this->setProtectedOrPrivateProperty($controller, 'response', \Config\Services::response());
$response = $controller->updateCustomerAddress($addressId);
$this->assertSame(500, $response->getStatusCode());
$decoded = json_decode($response->getBody(), true);
$this->assertEquals('error', $decoded['status']);
$this->assertEquals('Failed to update customer address.', $decoded['message']);
}
public function testDeleteCustomerAddressMissingId()
{
$response = $this->controller->deleteCustomerAddress(null);
$this->assertEquals(422, $response->getStatusCode());
$this->assertStringContainsString('ID is required', $response->getJSON());
}
public function testDeleteCustomerAddressNotFound()
{
$mockCustomerAddressModel = $this->getMockBuilder(\App\Models\CustomerAddress::class)
->onlyMethods(['find'])
->disableOriginalConstructor()
->getMock();
$mockCustomerAddressModel->method('find')->willReturn(null);
// Inject mock into private property
$this->setProtectedOrPrivateProperty($this->controller, 'customerAddressModel', $mockCustomerAddressModel);
// Call the method and assert response
$response = $this->controller->deleteCustomerAddress(999);
// Assert
$this->assertEquals(404, $response->getStatusCode());
$decodedBody = json_decode($response->getBody(), true);
$this->assertEquals('error', $decodedBody['status']);
$this->assertEquals('Customer address with ID 999 not found.', $decodedBody['message']);
}
public function testDeleteCustomerAddressSuccessNonDefault()
{
$addressId = 1;
$address = [
'id' => $addressId,
'customer_id' => 1,
'is_default' => 0,
];
$mock = $this->createMock(CustomerAddress::class);
$mock->method('find')->with($addressId)->willReturn($address);
$mock->method('delete')->with($addressId)->willReturn(true);
// Inject mock using reflection
$this->setProtectedOrPrivateProperty($this->controller, 'customerAddressModel', $mock);
$response = $this->controller->deleteCustomerAddress($addressId);
$this->assertEquals(200, $response->getStatusCode());
$this->assertStringContainsString('deleted successfully', $response->getJSON());
}
public function testDeleteCustomerAddressSuccessDefault()
{
$addressId = 1;
$customerId = 101;
$defaultAddress = [
'id' => $addressId,
'customer_id' => $customerId,
'is_default' => 1,
];
$anotherAddress = [
'id' => 2,
'customer_id' => $customerId,
'is_default' => 0,
];
$mock = $this->getMockBuilder(CustomerAddress::class)
->onlyMethods(['find', 'delete', 'first', 'update'])
->addMethods(['where', 'orderBy'])
->disableOriginalConstructor()
->getMock();
$mock->method('find')->with($addressId)->willReturn($defaultAddress);
$mock->method('delete')->with($addressId)->willReturn(true);
$mock->method('where')->willReturnSelf();
$mock->method('orderBy')->willReturnSelf();
$mock->method('first')->willReturn($anotherAddress);
$mock->method('update')->with($anotherAddress['id'], ['is_default' => 1])->willReturn(true);
// Inject mock using reflection
$this->setProtectedOrPrivateProperty($this->controller, 'customerAddressModel', $mock);
$response = $this->controller->deleteCustomerAddress($addressId);
$this->assertEquals(200, $response->getStatusCode());
$this->assertStringContainsString('deleted successfully', $response->getJSON());
}
public function testDeleteCustomerAddressFailsToDelete()
{
$addressId = 1;
$address = [
'id' => $addressId,
'customer_id' => 1,
'is_default' => 0,
];
$mock = $this->createMock(CustomerAddress::class);
$mock->method('find')->with($addressId)->willReturn($address);
$mock->method('delete')->with($addressId)->willReturn(false);
// Inject mock using reflection
$this->setProtectedOrPrivateProperty($this->controller, 'customerAddressModel', $mock);
$response = $this->controller->deleteCustomerAddress($addressId);
$this->assertEquals(500, $response->getStatusCode());
$this->assertStringContainsString('Failed to delete', $response->getJSON());
}
public function testSetDefaultCustomerAddressSuccess()
{
$customerId = 123;
$addressId = 456;
// Mock request
$this->request->method('getVar')->with('customer_id')->willReturn($customerId);
$this->customerModelMock = $this->getMockBuilder(\App\Models\Customer::class)
->addMethods(['where'])
->onlyMethods(['first'])
->disableOriginalConstructor()
->getMock();
$this->customerModelMock->method('where')->willReturnSelf();
$this->customerModelMock->method('first')->willReturn([
'id' => $customerId,
'status' => 'active',
]);
// Mock address model
$this->customerAddressModelMock = $this->getMockBuilder(\App\Models\CustomerAddress::class)
->addMethods(['where'])
->onlyMethods(['first', 'set', 'update'])
->disableOriginalConstructor()
->getMock();
$this->customerAddressModelMock->method('where')->willReturnSelf();
$this->customerAddressModelMock->method('first')->willReturn([
'id' => $addressId,
'customer_id' => $customerId,
]);
$this->customerAddressModelMock->method('set')->willReturnSelf();
$this->customerAddressModelMock->method('update')->willReturn(true);
// Inject mocks into service container
\Config\Services::injectMock('customer', $this->customerModelMock);
\Config\Services::injectMock('customeraddress', $this->customerAddressModelMock);
// Set mocked model into the controller before calling the method
$this->setProtectedOrPrivateProperty($this->controller, 'customerModel', $this->customerModelMock);
$this->setProtectedOrPrivateProperty($this->controller, 'customerAddressModel', $this->customerAddressModelMock);
// Capture response
$response = $this->controller->setDefaultCustomerAddress($addressId);
$responseArray = json_decode($response->getBody(), true);
$this->assertEquals(200, $response->getStatusCode());
$this->assertEquals('success', $responseArray['status']);
$this->assertEquals("Address ID {$addressId} is now the default for customer ID {$customerId}.", $responseArray['message']);
$this->assertEquals($customerId, $responseArray['data']['customer_id']);
$this->assertEquals($addressId, $responseArray['data']['address_id']);
}
public function testSetDefaultCustomerAddressMissingCustomerId()
{
// Arrange
$addressId = 456;
// Simulate request missing 'customer_id'
$this->request->method('getVar')->with('customer_id')->willReturn(null);
// Act
$response = $this->controller->setDefaultCustomerAddress($addressId);
$responseArray = json_decode($response->getBody(), true);
// Assert
$this->assertEquals(422, $response->getStatusCode());
$this->assertEquals('error', $responseArray['status']);
$this->assertEquals('Customer ID is required.', $responseArray['message']);
$this->assertNull($responseArray['data']);
}
public function testSetDefaultCustomerAddressFailsWhenCustomerNotFound()
{
$customerId = 123;
$addressId = 456;
// Mock request
$this->request->method('getVar')->with('customer_id')->willReturn($customerId);
// Mock customer model to return null
$this->customerModelMock = $this->getMockBuilder(\App\Models\Customer::class)
->addMethods(['where'])
->onlyMethods(['first'])
->disableOriginalConstructor()
->getMock();
$this->customerModelMock->method('where')->willReturnSelf();
$this->customerModelMock->method('first')->willReturn(null); // customer not found
\Config\Services::injectMock('customer', $this->customerModelMock);
$this->setProtectedOrPrivateProperty($this->controller, 'customerModel', $this->customerModelMock);
$response = $this->controller->setDefaultCustomerAddress($addressId);
$responseArray = json_decode($response->getBody(), true);
$this->assertEquals(404, $response->getStatusCode());
$this->assertEquals('error', $responseArray['status']);
$this->assertEquals('Customer not found or inactive.', $responseArray['message']);
$this->assertNull($responseArray['data']);
}
public function testSetDefaultCustomerAddressFailsWhenAddressNotFound()
{
$customerId = 123;
$addressId = 456;
$this->request->method('getVar')->with('customer_id')->willReturn($customerId);
// Mock valid customer
$this->customerModelMock = $this->getMockBuilder(\App\Models\Customer::class)
->addMethods(['where'])
->onlyMethods(['first'])
->disableOriginalConstructor()
->getMock();
$this->customerModelMock->method('where')->willReturnSelf();
$this->customerModelMock->method('first')->willReturn([
'id' => $customerId,
'status' => 'active',
]);
// Mock address not found
$this->customerAddressModelMock = $this->getMockBuilder(\App\Models\CustomerAddress::class)
->addMethods(['where'])
->onlyMethods(['first'])
->disableOriginalConstructor()
->getMock();
$this->customerAddressModelMock->method('where')->willReturnSelf();
$this->customerAddressModelMock->method('first')->willReturn(null);
\Config\Services::injectMock('customer', $this->customerModelMock);
\Config\Services::injectMock('customeraddress', $this->customerAddressModelMock);
$this->setProtectedOrPrivateProperty($this->controller, 'customerModel', $this->customerModelMock);
$this->setProtectedOrPrivateProperty($this->controller, 'customerAddressModel', $this->customerAddressModelMock);
$response = $this->controller->setDefaultCustomerAddress($addressId);
$responseArray = json_decode($response->getBody(), true);
$this->assertEquals(404, $response->getStatusCode());
$this->assertEquals('error', $responseArray['status']);
$this->assertEquals('Address not found or does not belong to the customer.', $responseArray['message']);
$this->assertNull($responseArray['data']);
}
}
+629
View File
@@ -0,0 +1,629 @@
<?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());
}
}
+49
View File
@@ -0,0 +1,49 @@
<?php
use CodeIgniter\Test\CIUnitTestCase;
use Config\App;
use Tests\Support\Libraries\ConfigReader;
/**
* @internal
*/
final class HealthTest extends CIUnitTestCase
{
public function testIsDefinedAppPath(): void
{
$this->assertTrue(defined('APPPATH'));
}
public function testBaseUrlHasBeenSet(): void
{
$validation = service('validation');
$env = false;
// Check the baseURL in .env
if (is_file(HOMEPATH . '.env')) {
$env = preg_grep('/^app\.baseURL = ./', file(HOMEPATH . '.env')) !== false;
}
if ($env) {
// BaseURL in .env is a valid URL?
// phpunit.xml.dist sets app.baseURL in $_SERVER
// So if you set app.baseURL in .env, it takes precedence
$config = new App();
$this->assertTrue(
$validation->check($config->baseURL, 'valid_url'),
'baseURL "' . $config->baseURL . '" in .env is not valid URL',
);
}
// Get the baseURL in app/Config/App.php
// You can't use Config\App, because phpunit.xml.dist sets app.baseURL
$reader = new ConfigReader();
// BaseURL in app/Config/App.php is a valid URL?
$this->assertTrue(
$validation->check($reader->baseURL, 'valid_url'),
'baseURL "' . $reader->baseURL . '" in app/Config/App.php is not valid URL',
);
}
}
+445
View File
@@ -0,0 +1,445 @@
<?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']);
}
}
+288
View File
@@ -0,0 +1,288 @@
<?php
namespace App\Tests\Controllers\Backend;
use App\Controllers\Backend\UserController;
use App\Models\User;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\HTTP\IncomingRequest;
class UserTest extends CIUnitTestCase
{
protected $mockUser;
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 setUp(): void
{
parent::setUp();
$this->mockUser = $this->createMock(User::class);
$this->controller = new UserController();
$this->setProtectedOrPrivateProperty($this->controller, 'user', $this->mockUser);
$this->setProtectedOrPrivateProperty($this->controller, 'request', $this->createMock(IncomingRequest::class));
$this->setProtectedOrPrivateProperty($this->controller, 'response', service('response'));
}
public function testIndexReturnsUsers()
{
$users = [
['id' => 1, 'username' => 'john', 'name' => 'John Doe'],
['id' => 2, 'username' => 'jane', 'name' => 'Jane Doe'],
];
$this->mockUser->method('findAll')->willReturn($users);
$result = $this->controller->index();
$this->assertEquals(200, $result->getStatusCode());
$data = json_decode($result->getBody(), true);
$this->assertIsArray($data);
$this->assertArrayHasKey('status', $data);
$this->assertArrayHasKey('message', $data);
$this->assertArrayHasKey('data', $data);
$this->assertEquals('success', $data['status']);
$this->assertEquals('User data retrieved successfully.', $data['message']);
$this->assertEquals($users, $data['data']);
}
public function testIndexReturnsNoUsers()
{
$this->mockUser->method('findAll')->willReturn([]);
$result = $this->controller->index();
$this->assertEquals(200, $result->getStatusCode());
$data = json_decode($result->getBody(), true);
$this->assertIsArray($data);
$this->assertArrayHasKey('status', $data);
$this->assertArrayHasKey('message', $data);
$this->assertArrayHasKey('data', $data);
$this->assertEquals('error', $data['status']);
$this->assertEquals('No user data found.', $data['message']);
$this->assertEquals(null, $data['data']);
}
public function testShowReturnsUser()
{
$user = ['id' => 1, 'username' => 'john', 'name' => 'John Doe'];
$this->mockUser->method('find')->with(1)->willReturn($user);
$result = $this->controller->show(1);
$this->assertEquals(200, $result->getStatusCode());
$data = json_decode($result->getBody(), true);
$this->assertIsArray($data);
$this->assertArrayHasKey('status', $data);
$this->assertArrayHasKey('message', $data);
$this->assertArrayHasKey('data', $data);
$this->assertEquals('success', $data['status']);
$this->assertEquals('User retrieved successfully.', $data['message']);
$this->assertEquals($user, $data['data']);
}
public function testShowReturnsNotFound()
{
$this->mockUser->method('find')->with(99)->willReturn(null);
$result = $this->controller->show(99);
$this->assertEquals(404, $result->getStatusCode());
$data = json_decode($result->getBody(), true);
$this->assertIsArray($data);
$this->assertArrayHasKey('status', $data);
$this->assertArrayHasKey('message', $data);
$this->assertEquals('error', $data['status']);
$this->assertEquals('User not found.', $data['message']);
}
public function testCreateValidationFails()
{
$controller = $this->getMockBuilder(UserController::class)
->onlyMethods(['validate'])
->enableOriginalConstructor()
->getMock();
$controller->method('validate')->willReturn(false);
$validator = $this->getMockBuilder(\CodeIgniter\Validation\Validation::class)
->disableOriginalConstructor()
->getMock();
$validator->method('getErrors')->willReturn(['username' => 'Required']);
$this->setProtectedOrPrivateProperty($controller, 'validator', $validator);
$this->setProtectedOrPrivateProperty($controller, 'user', $this->mockUser);
$this->setProtectedOrPrivateProperty($controller, 'request', $this->createMock(\CodeIgniter\HTTP\IncomingRequest::class));
$this->setProtectedOrPrivateProperty($controller, 'response', service('response'));
$result = $controller->create();
$this->assertEquals(422, $result->getStatusCode());
$data = json_decode($result->getBody(), true);
$this->assertIsArray($data);
$this->assertArrayHasKey('status', $data);
$this->assertArrayHasKey('message', $data);
$this->assertArrayHasKey('error', $data);
$this->assertEquals('errors', $data['status']);
$this->assertEquals('Validation failed', $data['message']);
$this->assertArrayHasKey('username', $data['errors']);
}
public function testCreateSuccess()
{
$this->mockUser->method('insert')->willReturn(1);
$this->mockUser->method('find')->with(1)->willReturn([
'id' => 1, 'username' => 'john', 'name' => 'John Doe'
]);
$request = $this->createMock(IncomingRequest::class);
$request->method('getVar')->willReturnMap([
['username', 'john'],
['name', 'John Doe'],
['password_hash', 'password'],
['role', 'admin'],
['status', 'active'],
]);
$controller = $this->getMockBuilder(UserController::class)
->onlyMethods(['validate'])
->enableOriginalConstructor()
->getMock();
$controller->method('validate')->willReturn(true);
$this->setProtectedOrPrivateProperty($controller, 'request', $request);
$this->setProtectedOrPrivateProperty($controller, 'user', $this->mockUser);
$this->setProtectedOrPrivateProperty($controller, 'response', service('response'));
$result = $controller->create();
$this->assertEquals(201, $result->getStatusCode());
$data = json_decode($result->getBody(), true);
$this->assertIsArray($data);
$this->assertArrayHasKey('status', $data);
$this->assertArrayHasKey('message', $data);
$this->assertArrayHasKey('data', $data);
$this->assertEquals('success', $data['status']);
$this->assertEquals('User created successfully.', $data['message']);
$this->assertEquals([
'id' => 1, 'username' => 'john', 'name' => 'John Doe'
], $data['data']);
}
public function testUpdateUserNotFound()
{
$this->mockUser->method('find')->with(99)->willReturn(null);
$result = $this->controller->update(99);
$this->assertEquals(404, $result->getStatusCode());
$data = json_decode($result->getBody(), true);
$this->assertIsArray($data);
$this->assertArrayHasKey('status', $data);
$this->assertArrayHasKey('message', $data);
$this->assertEquals('error', $data['status']);
$this->assertEquals('User not found.', $data['message']);
}
public function testUpdateNoDataProvided()
{
$this->mockUser->method('find')->with(1)->willReturn(['id' => 1]);
$request = $this->createMock(IncomingRequest::class);
$request->method('getRawInput')->willReturn([]);
$request->method('getPost')->willReturn([]);
$this->setProtectedOrPrivateProperty($this->controller, 'request', $request);
$result = $this->controller->update(1);
$this->assertEquals(400, $result->getStatusCode());
$data = json_decode($result->getBody(), true);
$this->assertIsArray($data);
$this->assertArrayHasKey('status', $data);
$this->assertArrayHasKey('message', $data);
$this->assertEquals('error', $data['status']);
$this->assertEquals('No data provided to update.', $data['message']);
}
public function testUpdateSuccess()
{
$this->mockUser->method('find')->with(1)->willReturn(['id' => 1]);
$this->mockUser->expects($this->once())->method('update')->with(1, ['username' => 'john']);
$this->mockUser->method('find')->with(1)->willReturn(['id' => 1, 'username' => 'john']);
$request = $this->createMock(IncomingRequest::class);
$request->method('getRawInput')->willReturn(['username' => 'john']);
$request->method('getPost')->willReturn([]);
$this->setProtectedOrPrivateProperty($this->controller, 'request', $request);
$result = $this->controller->update(1);
$this->assertEquals(200, $result->getStatusCode());
$data = json_decode($result->getBody(), true);
$this->assertIsArray($data);
$this->assertArrayHasKey('status', $data);
$this->assertArrayHasKey('message', $data);
$this->assertArrayHasKey('data', $data);
$this->assertEquals('success', $data['status']);
$this->assertEquals('User updated successfully.', $data['message']);
$this->assertEquals(['id' => 1, 'username' => 'john'], $data['data']);
}
public function testDeleteUserNotFound()
{
$this->mockUser->method('find')->with(99)->willReturn(null);
$result = $this->controller->delete(99);
$this->assertEquals(404, $result->getStatusCode());
$data = json_decode($result->getBody(), true);
$this->assertIsArray($data);
$this->assertArrayHasKey('status', $data);
$this->assertArrayHasKey('message', $data);
$this->assertEquals('error', $data['status']);
$this->assertEquals('User not found.', $data['message']);
}
public function testDeleteSuccess()
{
$this->mockUser->method('find')->with(1)->willReturn(['id' => 1]);
$this->mockUser->expects($this->once())->method('delete')->with(1);
$result = $this->controller->delete(1);
$this->assertEquals(200, $result->getStatusCode());
$data = json_decode($result->getBody(), true);
$this->assertIsArray($data);
$this->assertArrayHasKey('status', $data);
$this->assertArrayHasKey('message', $data);
$this->assertEquals('success', $data['status']);
$this->assertEquals('User deleted successfully.', $data['message']);
}
}