217 lines
7.0 KiB
PHP
217 lines
7.0 KiB
PHP
<?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/Pusher,CI 环境无可用 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,
|
||
];
|
||
}
|