initial: bootstrap from BukidBountyApp base

This commit is contained in:
Jonathan Sykes
2026-06-06 18:43:00 +08:00
commit eb4a5731fb
5674 changed files with 160857 additions and 0 deletions

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Tests\Feature;
use Tests\TestCase;
/**
* @internal
* @coversNothing
*/
class ExampleTest extends TestCase
{
public function testTheApplicationReturnsSuccessfulResponse()
{
$this->get('/')
->assertSuccessful();
}
}

View File

@@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
namespace Tests\Feature;
use App\Models\GlobalTransaction;
use App\Models\User;
use App\Enums\Market\ProductTransactionType;
use App\Enums\Market\TransactionFlow;
use Tests\TestCase;
use Hypervel\Support\Facades\Auth;
use Hypervel\Foundation\Testing\RefreshDatabase;
class GlobalTransactionFlowTest extends TestCase
{
use RefreshDatabase;
public function test_transaction_creation_assigns_correct_flow()
{
$user = factory(User::class)->create();
Auth::login($user);
// Test INCOME flow (STORE_SALE)
$response = $this->post('/admin/transactions/create', [
'amount' => 100,
'type' => ProductTransactionType::STORE_SALE->value,
'description' => 'Test Sale'
]);
$response->assertStatus(200);
$hashkey = $response->json('hashkey');
$transaction = GlobalTransaction::where('hashkey', $hashkey)->first();
$this->assertEquals(TransactionFlow::INCOME, $transaction->flow);
// Test EXPENSE flow (PURCHASE)
$response = $this->post('/admin/transactions/create', [
'amount' => 50,
'type' => ProductTransactionType::PURCHASE->value,
'description' => 'Test Purchase'
]);
$response->assertStatus(200);
$hashkey = $response->json('hashkey');
$transaction = GlobalTransaction::where('hashkey', $hashkey)->first();
$this->assertEquals(TransactionFlow::EXPENSE, $transaction->flow);
// Test NEUTRAL flow (INVENTORY_ADJUSTMENT)
$response = $this->post('/admin/transactions/create', [
'amount' => 0,
'type' => ProductTransactionType::INVENTORY_ADJUSTMENT->value,
'description' => 'Test Adjustment'
]);
$response->assertStatus(200);
$hashkey = $response->json('hashkey');
$transaction = GlobalTransaction::where('hashkey', $hashkey)->first();
$this->assertEquals(TransactionFlow::NEUTRAL, $transaction->flow);
}
public function test_transaction_list_returns_flow_data()
{
$user = factory(User::class)->create();
Auth::login($user);
$response = $this->post('/admin/transactions/list');
$response->assertStatus(200);
$data = $response->json();
if (count($data) > 0) {
$this->assertArrayHasKey('flow', $data[0]);
$this->assertArrayHasKey('value', $data[0]['flow']);
$this->assertArrayHasKey('label', $data[0]['flow']);
}
}
}

View File

@@ -0,0 +1,118 @@
<?php
declare(strict_types=1);
namespace Tests\Feature;
use App\Models\User;
use App\Models\Market\Store;
use App\Models\Market\PosSession;
use App\Enums\UserTypes;
use App\Enums\UserActions;
use Tests\TestCase;
use Hypervel\Support\Facades\Auth;
use Hypervel\Foundation\Testing\RefreshDatabase;
use Hyperf\Stringable\Str;
class PosAccessTest extends TestCase
{
use RefreshDatabase;
protected function createUser(UserTypes $type, User $parent = null)
{
return User::create([
'name' => 'Test ' . $type->value,
'fullname' => 'Test User ' . $type->value,
'username' => 'test_' . str_replace(' ', '_', $type->value) . '_' . Str::random(5),
'mobile_number' => '09' . mt_rand(100000000, 999999999),
'email' => Str::random(10) . '@example.com',
'password' => password_hash('123123', PASSWORD_DEFAULT),
'acct_type' => $type,
'parentuid' => $parent ? $parent->id : null,
'hashkey' => Str::random(100),
'active' => true,
]);
}
protected function createStore(User $owner, User $manager = null)
{
return Store::create([
'name' => 'Test Store ' . Str::random(5),
'owner_id' => $owner->id,
'manager_id' => $manager ? $manager->id : $owner->id,
'hashkey' => Str::random(100),
'is_active' => true,
'status' => 'active',
]);
}
public function test_pos_access_hierarchy()
{
// 1. Setup Hierarchy
$owner = $this->createUser(UserTypes::STORE_OWNER);
$store = $this->createStore($owner);
$manager = $this->createUser(UserTypes::STORE_MANAGER, $owner);
$store->manager_id = $manager->id;
$store->save();
$terminal = $this->createUser(UserTypes::POS_TERMINAL, $manager);
// Another store hierarchy
$otherOwner = $this->createUser(UserTypes::STORE_OWNER);
$otherStore = $this->createStore($otherOwner);
// 2. Test Authorized Access
// Owner access
Auth::login($owner);
$response = $this->post('/api/pos/start', ['store_hash' => $store->hashkey]);
$response->assertStatus(200);
// Manager access
Auth::login($manager);
$response = $this->post('/api/pos/start', ['store_hash' => $store->hashkey]);
$response->assertStatus(200);
// Terminal access (child of manager)
Auth::login($terminal);
$response = $this->post('/api/pos/start', ['store_hash' => $store->hashkey]);
$response->assertStatus(200);
// 3. Test Unauthorized Access
// Terminal accessing another store
$response = $this->post('/api/pos/start', ['store_hash' => $otherStore->hashkey]);
$response->assertStatus(403);
$this->assertEquals('You are not authorized to start a POS session for this store.', $response->json('message'));
// Manager accessing another store
Auth::login($manager);
$response = $this->post('/api/pos/start', ['store_hash' => $otherStore->hashkey]);
$response->assertStatus(403);
// 4. Test Other Endpoints
Auth::login($terminal);
// getPosSessions
$response = $this->post('/api/pos/sessions/list', ['store_hash' => $store->hashkey]);
$response->assertStatus(200);
$response = $this->post('/api/pos/sessions/list', ['store_hash' => $otherStore->hashkey]);
$response->assertStatus(403);
// getTodayStats
$response = $this->post('/api/pos/stats', ['store_hash' => $store->hashkey]);
$response->assertStatus(200);
$response = $this->post('/api/pos/stats', ['store_hash' => $otherStore->hashkey]);
$response->assertStatus(403);
// getCustomers
$response = $this->post('/api/pos/get-customers', ['store_hash' => $store->hashkey]);
$response->assertStatus(200);
$response = $this->post('/api/pos/get-customers', ['store_hash' => $otherStore->hashkey]);
$response->assertStatus(403);
}
}

View File

@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Tests\Feature;
use App\Models\User;
use Hypervel\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
/**
* @internal
* @coversNothing
*/
class RefreshDatabaseTest extends TestCase
{
use RefreshDatabase;
public function testCreateUser()
{
$user = factory(User::class)->create();
$this->assertDatabaseHas('users', [
'id' => $user->id,
]);
}
public function testCreateMultipleUsers()
{
factory(User::class, $count = 5)->create();
$this->assertDatabaseCount('users', $count);
}
public function testZeroUserAfterRefresh()
{
$this->assertSame(0, User::count());
}
}

13
tests/TestCase.php Normal file
View File

@@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace Tests;
use Hypervel\Foundation\Testing\Concerns\RunTestsInCoroutine;
use Hypervel\Foundation\Testing\TestCase as BaseTestCase;
abstract class TestCase extends BaseTestCase
{
use RunTestsInCoroutine;
}

View File

@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace Tests\Unit;
use Hypervel\Foundation\Testing\TestCase;
/**
* @internal
* @coversNothing
*/
class ExampleTest extends TestCase
{
/**
* A basic test example.
*/
public function testThatTrueIsTrue(): void
{
$this->assertTrue(true);
}
}

38
tests/bootstrap.php Normal file
View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
use Hypervel\Foundation\Testing\TestScanHandler;
ini_set('display_errors', 'on');
ini_set('display_startup_errors', 'on');
error_reporting(E_ALL);
! defined('SWOOLE_HOOK_FLAGS') && define('SWOOLE_HOOK_FLAGS', SWOOLE_HOOK_ALL);
$dir = __DIR__;
$lastDir = '';
if (! defined('BASE_PATH')) {
while (! file_exists($dir . '/composer.json') && $dir !== dirname($dir)) {
if ($lastDir === $dir) {
break;
}
$lastDir = $dir;
$dir = dirname($dir);
}
}
if (! file_exists($dir . '/composer.json')) {
throw new RuntimeException('Unable to find base path (directory with composer.json)');
}
define('BASE_PATH', $dir);
require BASE_PATH . '/vendor/autoload.php';
Hypervel\Foundation\ClassLoader::init(null, null, new TestScanHandler());
$app = require BASE_PATH . '/bootstrap/app.php';
$app->get(Hyperf\Contract\ApplicationInterface::class);

15
tests/helpers.php Normal file
View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
use Hyperf\Testing\ModelFactory;
use Hypervel\Context\ApplicationContext;
if (! function_exists('factory')) {
function factory(string $class, mixed ...$arguments)
{
return ApplicationContext::getContainer()
->get(ModelFactory::class)
->factory($class, ...$arguments);
}
}