Files
lotteryLaravel/tests/Pest.php
wchino 15bd997c4e
Some checks failed
lotterLaravel CI / test (push) Has been cancelled
lotterLaravel E2E / e2e-api (push) Has been cancelled
feat(core): harden sessions settlement and credit activity
2026-07-22 20:53:43 +08:00

217 lines
7.0 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
use Tests\TestCase;
use App\Models\AdminUser;
use App\Models\AgentProfile;
use Illuminate\Support\Carbon;
use App\Support\SuperAdminAccount;
use Illuminate\Support\Facades\DB;
use App\Events\OddsUpdateBroadcast;
use App\Events\PlayToggleBroadcast;
use App\Events\RiskSoldOutBroadcast;
use App\Events\RiskWarningBroadcast;
use App\Support\PlatformSystemRoles;
use App\Events\JackpotBurstBroadcast;
use App\Events\BalanceUpdateBroadcast;
use App\Events\DrawCountdownBroadcast;
use Illuminate\Support\Facades\Schema;
use App\Contracts\WalletApiDnsResolver;
use App\Events\DrawStatusChangeBroadcast;
use App\Events\PlayCatalogUpdatedBroadcast;
use App\Events\DrawResultPublishedBroadcast;
use App\Events\PlayerSessionReplacedBroadcast;
use App\Support\Integration\WalletApiRequestGuard;
use Illuminate\Foundation\Testing\RefreshDatabase;
/*
|--------------------------------------------------------------------------
| Test Case
|--------------------------------------------------------------------------
|
| The closure you provide to your test functions is always bound to a specific PHPUnit test
| case class. By default, that class is "PHPUnit\Framework\TestCase". Of course, you may
| need to change it using the "pest()" function to bind different classes or traits.
|
*/
pest()->extend(TestCase::class)
// ->use(RefreshDatabase::class)
->in('Feature');
pest()->extend(TestCase::class)->in('Unit');
/*
|--------------------------------------------------------------------------
| Expectations
|--------------------------------------------------------------------------
|
| When you're writing tests, you often need to check that values meet certain conditions. The
| "expect()" function gives you access to a set of "expectations" methods that you can use
| to assert different things. Of course, you may extend the Expectation API at any time.
|
*/
expect()->extend('toBeOne', function () {
return $this->toBe(1);
});
/*
|--------------------------------------------------------------------------
| Functions
|--------------------------------------------------------------------------
|
| While Pest is very powerful out-of-the-box, you may have some testing code specific to your
| project that you don't want to repeat in every file. Here you can also expose helpers as
| global functions to help you to reduce the number of lines of code in your test files.
|
*/
/** 为后台测试账号挂上唯一超级管理员(不绑定站点)。 */
function grantSuperAdminRole(AdminUser $admin): void
{
PlatformSystemRoles::ensureSuperAdminRole();
SuperAdminAccount::assign($admin);
}
/** 为后台测试账号挂上代理节点(需已存在 agent_nodes / admin_user_agents 表)。 */
/** Feature 测直调 {@see AgentNodeService::createChild()} 时使用的密码。 */
function agentNodeTestPassword(): string
{
return '12345678';
}
/** @param array<string, mixed> $overrides
* @return array<string, mixed>
*/
function agentChildPayload(array $overrides = []): array
{
return array_merge([
'password' => agentNodeTestPassword(),
'can_create_child_agent' => true,
'can_create_player' => true,
], $overrides);
}
/**
* 为钱包 HTTP 测试提供离线 DNS避免测试依赖机器或公网解析状态。
*
* @param array<string, list<string>> $recordsByHost
* @param list<string> $defaultRecords
*/
function fakeWalletApiDns(
array $recordsByHost = [],
array $defaultRecords = ['93.184.216.34'],
): void {
app()->instance(WalletApiDnsResolver::class, new class($recordsByHost, $defaultRecords) implements WalletApiDnsResolver
{
/**
* @param array<string, list<string>> $recordsByHost
* @param list<string> $defaultRecords
*/
public function __construct(
private readonly array $recordsByHost,
private readonly array $defaultRecords,
) {}
public function resolveAll(string $hostname): array
{
return $this->recordsByHost[$hostname] ?? $this->defaultRecords;
}
});
// Guard 可能已在当前测试中解析过,清除后确保使用刚绑定的 DNS resolver。
app()->forgetInstance(WalletApiRequestGuard::class);
}
function bindAdminUserToAgent(AdminUser $admin, int $agentNodeId): void
{
DB::table('admin_user_agents')->updateOrInsert(
['admin_user_id' => $admin->id],
[
'agent_node_id' => $agentNodeId,
'is_primary' => true,
'granted_at' => now(),
],
);
}
/** 确保默认站点一级代理已有占成/授信档案(子代理创建与额度校验依赖)。 */
function ensureRootAgentProfileSeeded(): void
{
$rootId = DB::table('agent_nodes')->where('depth', 0)->value('id');
if ($rootId === null) {
return;
}
AgentProfile::query()->updateOrCreate(
['agent_node_id' => (int) $rootId],
[
'total_share_rate' => 100,
'credit_limit' => 1_000_000,
'allocated_credit' => 0,
'used_credit' => 0,
'rebate_limit' => 1,
'default_player_rebate' => 0,
],
);
}
function ensureAdminActionCatalogSeeded(): void
{
if (! Schema::hasTable('admin_action_catalog')) {
Schema::create('admin_action_catalog', static function ($table): void {
$table->id();
$table->string('code')->unique();
$table->string('name');
$table->unsignedTinyInteger('status')->default(1);
$table->timestamps();
});
}
$now = Carbon::now();
foreach ([
['code' => 'view', 'name' => '查看'],
['code' => 'manage', 'name' => '管理'],
] as $row) {
DB::table('admin_action_catalog')->updateOrInsert(
['code' => $row['code']],
[
'name' => $row['name'],
'status' => 1,
'created_at' => $now,
'updated_at' => $now,
],
);
}
}
/**
* 返回项目内所有 Broadcast 事件类列表,供 Event::fake() 拦截广播使用。
*
* 业务切到 reverb driver 后fake 列表不全会导致未拦截的 broadcast 真实派发、
* 真实连接 Reverb/PusherCI 环境无可用 server 时直接 500。本 helper 保证:
* Event::fake(allBroadcastEvents())
* 一行覆盖流程中可能触发的所有事件。
*
* 不可使用 Event::fake() 无参形式:会同时拦截 Illuminate\Database\Events\* 等框架事件,
* 干扰 RefreshDatabase / model events / 自定义 boot hooks。
*
* @return list<class-string>
*/
function allBroadcastEvents(): array
{
return [
BalanceUpdateBroadcast::class,
DrawCountdownBroadcast::class,
DrawResultPublishedBroadcast::class,
DrawStatusChangeBroadcast::class,
JackpotBurstBroadcast::class,
OddsUpdateBroadcast::class,
PlayCatalogUpdatedBroadcast::class,
PlayToggleBroadcast::class,
PlayerSessionReplacedBroadcast::class,
RiskSoldOutBroadcast::class,
RiskWarningBroadcast::class,
];
}