\PDO::ERRMODE_EXCEPTION, \PDO::ATTR_TIMEOUT => 3, ]); $server = $pdo->query('SELECT @@datadir AS data_dir, @@socket AS socket_path, @@skip_networking AS network_disabled')->fetch(\PDO::FETCH_ASSOC); if (realpath(rtrim($server['data_dir'], '/')) !== realpath(dirname($socket) . '/data') || $server['socket_path'] !== $socket || (int)$server['network_disabled'] !== 1) { throw new \RuntimeException('Refusing a server whose datadir, socket or skip-networking does not match the fixture'); } return $pdo; } public static function laravel(string $socket, string $database): \Illuminate\Foundation\Application { self::guardedPdo($socket, $database); \Illuminate\Support\Facades\Facade::clearResolvedInstances(); $app = new \Illuminate\Foundation\Application(dirname(__DIR__, 2)); $app->instance('config', new \Illuminate\Config\Repository(['app' => ['locale' => 'zh', 'timezone' => 'Asia/Shanghai'], 'third_game' => ['currency' => 'CNY']])); $app->instance('events', new \Illuminate\Events\Dispatcher($app)); \Illuminate\Support\Facades\Facade::setFacadeApplication($app); $db = new \Illuminate\Database\Capsule\Manager($app); $db->addConnection(['driver' => 'mysql', 'unix_socket' => $socket, 'database' => $database, 'username' => 'root', 'password' => '', 'prefix' => 'bot_', 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci', 'strict' => true]); $db->setEventDispatcher($app['events']); $db->setAsGlobal(); $db->bootEloquent(); $app->instance('db', $db->getDatabaseManager()); $app->bind('db.schema', fn () => $db->schema()); $translator = new \Illuminate\Translation\Translator(new \Illuminate\Translation\ArrayLoader(), 'zh'); $app->instance('translator', $translator); $app->instance('validator', new \Illuminate\Validation\Factory($translator, $app)); $request = \Illuminate\Http\Request::create('/isolated-sharing-mysql-fixture'); $request->user = (object)['id' => 8, 'username' => 'fixture-admin']; $app->instance('request', $request); \Carbon\Carbon::setTestNow(\Carbon\Carbon::parse('2026-10-25 12:00:00', 'Asia/Shanghai')); return $app; } public static function schema(): void { $schema = \Illuminate\Support\Facades\DB::connection()->getSchemaBuilder(); $schema->create('users', function ($t) { $t->id(); $t->string('user_id'); $t->string('member_id')->unique(); $t->string('account'); $t->string('first_name'); $t->string('user_code'); $t->string('agent_user_code')->nullable(); $t->integer('from')->default(1); $t->dateTime('delete_time')->nullable(); $t->timestamps(); }); $schema->create('share_settings', function ($t) { $t->id(); $t->boolean('enabled')->default(0); $t->boolean('benefit_enabled')->default(0); $t->string('share_domain')->default(''); $t->decimal('valid_member_min_deposit', 20, 2)->default(20); $t->decimal('non_seller_threshold', 20, 2)->default(200); $t->decimal('tier_one_rate', 8, 4)->default(4); $t->decimal('tier_two_rate', 8, 4)->default(6); $t->unsignedBigInteger('operator_id')->nullable(); $t->string('operator_name')->default(''); $t->timestamps(); }); $schema->create('wallets', function ($t) { $t->id(); $t->string('member_id')->unique(); $t->decimal('available_balance', 24, 10)->default(20); $t->timestamps(); }); $schema->create('balance_logs', function ($t) { $t->id(); $t->string('member_id'); $t->integer('type')->default(1); $t->string('change_type'); foreach (['amount', 'before_balance', 'after_balance'] as $field) $t->decimal($field, 24, 10); $t->unsignedBigInteger('related_id')->nullable(); $t->text('remark')->nullable(); $t->timestamps(); }); $schema->create('operation_audits', function ($t) { $t->id(); $t->string('resource_type'); $t->unsignedBigInteger('resource_id')->nullable(); $t->string('action'); $t->json('changes'); $t->unsignedBigInteger('operator_id')->nullable(); $t->string('operator_name'); $t->timestamp('created_at')->useCurrent(); }); (require dirname(__DIR__, 2) . '/database/migrations/2026_10_24_120000_create_member_sharing_system.php')->up(); } public static function seed(): void { $db = '\\Illuminate\\Support\\Facades\\DB'; $old = '2026-10-01 00:00:00'; foreach ([1, 2] as $id) { $db::table('users')->insert(['id' => $id, 'user_id' => (string)(100 + $id), 'member_id' => 'M' . $id, 'account' => 'member' . $id, 'first_name' => 'fixture' . $id, 'user_code' => 'CODE' . $id, 'agent_user_code' => $id === 2 ? 'CODE1' : null, 'from' => 1, 'created_at' => $old, 'updated_at' => $old]); $db::table('share_members')->insert(['user_id' => $id, 'parent_user_id' => $id === 2 ? 1 : null, 'invitation_code' => 'CODE' . $id, 'binding_status' => $id === 2 ? 'matched' : 'root', 'level' => 1, 'bound_at' => $old, 'created_at' => $old, 'updated_at' => $old]); $db::table('share_tree')->insert(['ancestor_user_id' => $id, 'descendant_user_id' => $id, 'depth' => 0, 'bound_at' => $old]); } $db::table('share_tree')->insert(['ancestor_user_id' => 1, 'descendant_user_id' => 2, 'depth' => 1, 'bound_at' => $old]); $db::table('wallets')->insert(['member_id' => 'M1', 'available_balance' => '20', 'created_at' => $old, 'updated_at' => $old]); $db::table('share_accounts')->insert(['user_id' => 1, 'earned_amount' => '10.019', 'paid_amount' => 0, 'reserved_amount' => 0, 'created_at' => $old, 'updated_at' => $old]); $db::table('share_system_state')->insert(['key' => 'initialized_at', 'value' => $old, 'created_at' => $old, 'updated_at' => $old]); $db::table('share_settings')->insert(['enabled' => 0, 'benefit_enabled' => 1, 'share_domain' => 'https://fixture.example', 'activation_date' => '2026-10-24', 'valid_member_min_deposit' => 0, 'valid_member_min_effective_bet' => 0]); $rules = array_fill_keys(array_keys(\App\Services\Sharing\ShareDefaults::categories()), [['min_valid_members' => 0, 'min_performance' => '0', 'rate' => '10']]); $db::table('share_policies')->insert(['effective_date' => '2026-10-24', 'enabled' => 0, 'benefit_enabled' => 1, 'valid_member_min_deposit' => 0, 'valid_member_min_effective_bet' => 0, 'rules' => json_encode($rules)]); $db::table('share_bet_facts')->insert(['source' => 'fixture', 'source_id' => 'fact-1', 'user_id' => 2, 'member_id' => 'M2', 'category' => 'third:2', 'bet_at' => '2026-10-24 12:00:00', 'bet_date' => '2026-10-24', 'settled_at' => '2026-10-25 10:00:00', 'bet_amount' => 100, 'net_profit' => -100, 'effective_amount' => 100, 'settled' => 1, 'voided' => 0, 'currency' => 'CNY', 'source_version' => str_repeat('a', 64), 'created_at' => $old, 'updated_at' => $old]); } public static function hold(array $params): void { file_put_contents($params['locked'], 'locked'); $deadline = microtime(true) + 20; while (!is_file($params['release'])) { if (microtime(true) > $deadline) throw new \RuntimeException('Timed out waiting for the test to release its row lock'); usleep(10000); } } }