seed(CurrencySeeder::class); }); function mintCurrencyAdminToken(): string { $admin = AdminUser::query()->create([ 'username' => 'currency_admin', 'name' => 'Currency Admin', 'email' => null, 'password' => Hash::make('secret-strong'), 'status' => 0, ]); grantSuperAdminRole($admin); return $admin->createToken('test', ['*'], now()->addDay())->plainTextToken; } test('admin can list currencies', function (): void { $token = mintCurrencyAdminToken(); $this->withHeader('Authorization', 'Bearer '.$token) ->getJson('/api/v1/admin/currencies') ->assertOk() ->assertJsonPath('data.items.0.code', 'NPR'); }); test('admin can create currency and normalize code', function (): void { $token = mintCurrencyAdminToken(); $this->withHeader('Authorization', 'Bearer '.$token) ->postJson('/api/v1/admin/currencies', [ 'code' => 'eur', 'name' => 'Euro', 'decimal_places' => 2, 'is_enabled' => true, 'is_bettable' => true, ]) ->assertStatus(201) ->assertJsonPath('data.code', 'EUR') ->assertJsonPath('data.is_bettable', true); $this->assertDatabaseHas('currencies', [ 'code' => 'EUR', 'name' => 'Euro', 'is_enabled' => true, 'is_bettable' => true, ]); }); test('disabling currency forces bettable off', function (): void { $token = mintCurrencyAdminToken(); $currency = Currency::query()->where('code', 'USD')->firstOrFail(); $this->withHeader('Authorization', 'Bearer '.$token) ->putJson('/api/v1/admin/currencies/'.$currency->code, [ 'is_enabled' => false, 'is_bettable' => true, ]) ->assertOk() ->assertJsonPath('data.is_enabled', false) ->assertJsonPath('data.is_bettable', false); $currency->refresh(); expect($currency->is_enabled)->toBeFalse() ->and($currency->is_bettable)->toBeFalse(); }); test('enabling currency as bettable bootstraps odds items and jackpot pool', function (): void { $token = mintCurrencyAdminToken(); $currency = Currency::query()->where('code', 'USD')->firstOrFail(); PlayType::query()->create([ 'play_code' => 'STRAIGHT', 'category' => '4d', 'dimension' => 4, 'bet_mode' => 'single', 'display_name' => '直选', 'is_enabled' => true, 'sort_order' => 10, 'supports_multi_number' => false, 'reserved_rule_json' => null, ]); $version = OddsVersion::query()->create([ 'version_no' => 1, 'status' => ConfigVersionStatus::Active->value, 'updated_by' => null, 'reason' => 'seed', ]); OddsItem::query()->create([ 'version_id' => $version->id, 'play_code' => 'STRAIGHT', 'prize_scope' => 'first', 'odds_value' => 250000, 'rebate_rate' => 0.05, 'commission_rate' => 0.01, 'currency_code' => 'NPR', 'extra_config_json' => ['sample' => true], ]); JackpotPool::query()->updateOrCreate([ 'currency_code' => 'NPR', ], [ 'current_amount' => 12345, 'contribution_rate' => '0.0300', 'trigger_threshold' => 200000000, 'payout_rate' => '0.4000', 'force_trigger_draw_gap' => 88, 'min_bet_amount' => 200, 'combo_trigger_play_codes' => ['STRAIGHT'], 'status' => 1, 'last_trigger_draw_id' => null, ]); $this->withHeader('Authorization', 'Bearer '.$token) ->putJson('/api/v1/admin/currencies/'.$currency->code, [ 'is_bettable' => true, ]) ->assertOk() ->assertJsonPath('data.is_bettable', true); $currency->refresh(); expect($currency->is_bettable)->toBeTrue(); $this->assertDatabaseHas('jackpot_pools', [ 'currency_code' => 'USD', 'current_amount' => 0, 'contribution_rate' => '0.0300', 'trigger_threshold' => 200000000, 'payout_rate' => '0.4000', 'force_trigger_draw_gap' => 88, 'min_bet_amount' => 200, 'status' => 1, ]); $this->assertDatabaseHas('odds_items', [ 'version_id' => $version->id, 'play_code' => 'STRAIGHT', 'prize_scope' => 'first', 'currency_code' => 'USD', 'odds_value' => 250000, ]); $this->assertDatabaseHas('odds_items', [ 'version_id' => $version->id, 'play_code' => 'STRAIGHT', 'prize_scope' => 'second', 'currency_code' => 'USD', ]); });