|
|
@@ -1,157 +0,0 @@
|
|
|
-<?php
|
|
|
-
|
|
|
-namespace Tests\Integration;
|
|
|
-
|
|
|
-use App\Http\Controllers\api\Settle;
|
|
|
-use App\Models\BalanceLog;
|
|
|
-use App\Models\Bet;
|
|
|
-use App\Models\PcIssue;
|
|
|
-use Illuminate\Cache\ArrayStore;
|
|
|
-use Illuminate\Cache\Repository as CacheRepository;
|
|
|
-use Illuminate\Config\Repository as ConfigRepository;
|
|
|
-use Illuminate\Database\Capsule\Manager;
|
|
|
-use Illuminate\Database\Schema\Blueprint;
|
|
|
-use Illuminate\Foundation\Application;
|
|
|
-use Illuminate\Http\JsonResponse;
|
|
|
-use Illuminate\Http\Request;
|
|
|
-use Illuminate\Support\Facades\DB;
|
|
|
-use Illuminate\Support\Facades\Facade;
|
|
|
-use Illuminate\Translation\ArrayLoader;
|
|
|
-use Illuminate\Translation\Translator;
|
|
|
-use PHPUnit\Framework\Attributes\DataProvider;
|
|
|
-use PHPUnit\Framework\TestCase;
|
|
|
-use Psr\Log\NullLogger;
|
|
|
-
|
|
|
-/** Uses real settlement code with an in-memory database and cache, without application providers. */
|
|
|
-class SettleLedgerTest extends TestCase
|
|
|
-{
|
|
|
- private Application $app;
|
|
|
-
|
|
|
- protected function setUp(): void
|
|
|
- {
|
|
|
- Facade::clearResolvedInstances();
|
|
|
- $this->app = new Application(dirname(__DIR__, 2));
|
|
|
- $this->app->instance('config', new ConfigRepository(['app' => ['locale' => 'zh', 'timezone' => 'Asia/Shanghai']]));
|
|
|
- $this->app->instance('translator', new Translator(new ArrayLoader(), 'zh'));
|
|
|
- Facade::setFacadeApplication($this->app);
|
|
|
-
|
|
|
- $db = new Manager($this->app);
|
|
|
- $db->addConnection(['driver' => 'sqlite', 'database' => ':memory:', 'prefix' => 'bot_']);
|
|
|
- $db->setAsGlobal();
|
|
|
- $db->bootEloquent();
|
|
|
- $this->app->instance('db', $db->getDatabaseManager());
|
|
|
- $this->app->instance('cache', new CacheRepository(new ArrayStore()));
|
|
|
- $this->app->instance('log', new NullLogger());
|
|
|
- $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new \Tests\Support\JsonResponseFactory());
|
|
|
-
|
|
|
- foreach (['pc_issues', 'issues'] as $table) {
|
|
|
- $db->schema()->create($table, function (Blueprint $t) {
|
|
|
- $t->id();
|
|
|
- $t->string('issue_no');
|
|
|
- $t->string('winning_numbers');
|
|
|
- $t->integer('status');
|
|
|
- $t->dateTime('end_time');
|
|
|
- $t->timestamps();
|
|
|
- });
|
|
|
- }
|
|
|
- $db->schema()->create('gameplay_rules', function (Blueprint $t) {
|
|
|
- $t->id();
|
|
|
- $t->string('keywords');
|
|
|
- $t->decimal('odds', 10, 2);
|
|
|
- $t->timestamps();
|
|
|
- });
|
|
|
- $db->schema()->create('bets', function (Blueprint $t) {
|
|
|
- $t->id();
|
|
|
- $t->string('issue_no');
|
|
|
- $t->string('keywords');
|
|
|
- $t->decimal('amount', 20, 2);
|
|
|
- $t->decimal('odds', 10, 2);
|
|
|
- $t->integer('status');
|
|
|
- $t->decimal('profit', 20, 2);
|
|
|
- $t->integer('issue_id');
|
|
|
- $t->string('member_id');
|
|
|
- $t->string('user_id');
|
|
|
- $t->timestamps();
|
|
|
- });
|
|
|
- $db->schema()->create('wallets', function (Blueprint $t) {
|
|
|
- $t->id();
|
|
|
- $t->string('member_id')->unique();
|
|
|
- $t->decimal('available_balance', 20, 2);
|
|
|
- $t->timestamps();
|
|
|
- });
|
|
|
- $db->schema()->create('balance_logs', function (Blueprint $t) {
|
|
|
- $t->id();
|
|
|
- $t->string('member_id');
|
|
|
- $t->integer('type')->default(1);
|
|
|
- $t->decimal('amount', 20, 2);
|
|
|
- $t->decimal('before_balance', 20, 2);
|
|
|
- $t->decimal('after_balance', 20, 2);
|
|
|
- $t->string('change_type');
|
|
|
- $t->integer('related_id');
|
|
|
- $t->string('remark');
|
|
|
- $t->timestamps();
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- protected function tearDown(): void
|
|
|
- {
|
|
|
- DB::disconnect();
|
|
|
- Facade::clearResolvedInstances();
|
|
|
- parent::tearDown();
|
|
|
- }
|
|
|
-
|
|
|
- public static function settlementGames(): array
|
|
|
- {
|
|
|
- return [
|
|
|
- '极速28' => ['pc28', 'pc_issues', '极速28投注'],
|
|
|
- '加拿大28' => ['ca28', 'issues', '加拿大28投注'],
|
|
|
- ];
|
|
|
- }
|
|
|
-
|
|
|
- #[DataProvider('settlementGames')]
|
|
|
- public function test_settlement_ledger_matches_wallet_and_accumulated_consumption(string $game, string $issueTable, string $betType): void
|
|
|
- {
|
|
|
- DB::table($issueTable)->insert([
|
|
|
- 'issue_no' => '100001', 'winning_numbers' => '6,6,6',
|
|
|
- 'status' => PcIssue::STATUS_DRAW, 'end_time' => '2026-10-13 12:00:00',
|
|
|
- ]);
|
|
|
- DB::table('gameplay_rules')->insert(['keywords' => '大', 'odds' => '2.00']);
|
|
|
- DB::table('wallets')->insert(['member_id' => '1001', 'available_balance' => '1000.50']);
|
|
|
- // An existing real debit must accumulate with the injected debit rather than cancel it.
|
|
|
- DB::table('balance_logs')->insert([
|
|
|
- 'member_id' => '1001', 'amount' => '-100.25',
|
|
|
- 'before_balance' => '1100.75', 'after_balance' => '1000.50',
|
|
|
- 'change_type' => $betType, 'related_id' => 999, 'remark' => 'existing bet',
|
|
|
- ]);
|
|
|
- $this->assertSame('100.25', BalanceLog::getTotalConsume('1001'));
|
|
|
-
|
|
|
- $request = Request::create('/api/pcissue/verify', 'POST', [
|
|
|
- 'issue_no' => '100001', 'member_id' => '1001', 'keywords' => '大',
|
|
|
- 'amount' => '100.25', 'fake_time' => '2026-10-13 11:58:00', 'game' => $game,
|
|
|
- ]);
|
|
|
- $this->app->instance('request', $request);
|
|
|
- $result = (new Settle())->inject($request)->getData(true);
|
|
|
-
|
|
|
- $this->assertSame(200, $result['code'], $result['msg'] ?? '');
|
|
|
- $this->assertSame('200.50', BalanceLog::getTotalConsume('1001'));
|
|
|
- $walletBalance = DB::table('wallets')->where('member_id', '1001')->value('available_balance');
|
|
|
- $this->assertSame('1100.75', bcadd((string)$walletBalance, '0', 2));
|
|
|
- $this->assertSame('1100.75', bcadd((string)$result['balance_after'], '0', 2));
|
|
|
-
|
|
|
- $logs = DB::table('balance_logs')->where('related_id', $result['order_id'])->orderBy('id')->get();
|
|
|
- $this->assertCount(2, $logs);
|
|
|
- $this->assertSame($betType, $logs[0]->change_type);
|
|
|
- $this->assertSame('-100.25', bcadd((string)$logs[0]->amount, '0', 2));
|
|
|
- $this->assertSame('900.25', bcadd((string)$logs[0]->after_balance, '0', 2));
|
|
|
- $this->assertSame('中奖', $logs[1]->change_type);
|
|
|
- $this->assertSame('200.50', bcadd((string)$logs[1]->amount, '0', 2));
|
|
|
- $this->assertSame('1100.75', bcadd((string)$logs[1]->after_balance, '0', 2));
|
|
|
- $this->assertSame(0, bccomp((string)$logs[0]->after_balance, (string)$logs[1]->before_balance, 2));
|
|
|
- foreach ($logs as $log) {
|
|
|
- $this->assertSame(0, bccomp((string)$log->amount, bcsub((string)$log->after_balance, (string)$log->before_balance, 2), 2));
|
|
|
- }
|
|
|
- $ledgerChange = bcadd((string)$logs[0]->amount, (string)$logs[1]->amount, 2);
|
|
|
- $this->assertSame($ledgerChange, bcsub((string)$walletBalance, '1000.50', 2));
|
|
|
- $this->assertSame(Bet::STATUS_SETTLED, (int)DB::table('bets')->where('id', $result['order_id'])->value('status'));
|
|
|
- }
|
|
|
-}
|