|
@@ -1,245 +0,0 @@
|
|
|
-<?php
|
|
|
|
|
-
|
|
|
|
|
-namespace Tests\Integration;
|
|
|
|
|
-
|
|
|
|
|
-use App\Http\Controllers\api\Rebate;
|
|
|
|
|
-use App\Http\Middleware\CheckToken;
|
|
|
|
|
-use Carbon\Carbon;
|
|
|
|
|
-use Illuminate\Config\Repository;
|
|
|
|
|
-use Illuminate\Database\Capsule\Manager;
|
|
|
|
|
-use Illuminate\Database\Schema\Blueprint;
|
|
|
|
|
-use Illuminate\Events\Dispatcher;
|
|
|
|
|
-use Illuminate\Foundation\Application;
|
|
|
|
|
-use Illuminate\Http\JsonResponse;
|
|
|
|
|
-use Illuminate\Http\Request;
|
|
|
|
|
-use Illuminate\Routing\Router;
|
|
|
|
|
-use Illuminate\Support\Facades\DB;
|
|
|
|
|
-use Illuminate\Support\Facades\Facade;
|
|
|
|
|
-use Illuminate\Translation\ArrayLoader;
|
|
|
|
|
-use Illuminate\Translation\Translator;
|
|
|
|
|
-use Illuminate\Validation\Factory;
|
|
|
|
|
-use PHPUnit\Framework\TestCase;
|
|
|
|
|
-
|
|
|
|
|
-/** Real API routes, member-token middleware and queries, using SQLite without loading .env. */
|
|
|
|
|
-class MemberRebateApiTest extends TestCase
|
|
|
|
|
-{
|
|
|
|
|
- private Application $app;
|
|
|
|
|
- private Router $router;
|
|
|
|
|
-
|
|
|
|
|
- protected function setUp(): void
|
|
|
|
|
- {
|
|
|
|
|
- parent::setUp();
|
|
|
|
|
- Carbon::setTestNow(Carbon::parse('2026-10-25 00:30:00', 'Asia/Shanghai'));
|
|
|
|
|
- Facade::clearResolvedInstances();
|
|
|
|
|
- $this->app = new Application(dirname(__DIR__, 2));
|
|
|
|
|
- $this->app->instance('config', new Repository(['app' => ['locale' => 'zh', 'timezone' => 'Asia/Shanghai']]));
|
|
|
|
|
- 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->bind('db.schema', fn () => $db->schema());
|
|
|
|
|
- $translator = new Translator(new ArrayLoader(), 'zh');
|
|
|
|
|
- $this->app->instance('translator', $translator);
|
|
|
|
|
- $this->app->instance('validator', new Factory($translator, $this->app));
|
|
|
|
|
- $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new class {
|
|
|
|
|
- public function json($data, $status = 200, $headers = [], $options = 0) {
|
|
|
|
|
- return new JsonResponse($data, $status, $headers, $options);
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
- Request::macro('validate', function (array $rules) {
|
|
|
|
|
- return app('validator')->make($this->all(), $rules)->validate();
|
|
|
|
|
- });
|
|
|
|
|
- if (!function_exists('lang')) require_once dirname(__DIR__, 2) . '/app/Helpers/helpers.php';
|
|
|
|
|
- $db->schema()->create('users', function (Blueprint $t) {
|
|
|
|
|
- $t->id(); $t->string('user_id'); $t->string('member_id')->nullable();
|
|
|
|
|
- });
|
|
|
|
|
- $db->schema()->create('user_session', function (Blueprint $t) {
|
|
|
|
|
- $t->id(); $t->string('user_id'); $t->string('token');
|
|
|
|
|
- $t->bigInteger('expire_time'); $t->bigInteger('create_time'); $t->bigInteger('update_time');
|
|
|
|
|
- });
|
|
|
|
|
- foreach ([
|
|
|
|
|
- '2025_10_31_145445_create_rebates.php',
|
|
|
|
|
- '2025_10_31_155143_update_rebates.php',
|
|
|
|
|
- '2025_11_03_144118_update_rebates.php',
|
|
|
|
|
- '2025_11_20_145643_update_rebates.php',
|
|
|
|
|
- '2025_11_20_151650_update_rebates.php',
|
|
|
|
|
- '2026_01_13_153629_update_rebates.php',
|
|
|
|
|
- '2026_01_28_093600_update_rebate.php',
|
|
|
|
|
- ] as $migration) {
|
|
|
|
|
- (require dirname(__DIR__, 2) . '/database/migrations/' . $migration)->up();
|
|
|
|
|
- }
|
|
|
|
|
- foreach (['one' => '100001', 'two' => '200002', 'unlinked' => null] as $suffix => $memberId) {
|
|
|
|
|
- DB::table('users')->insert(['user_id' => 'login-' . $suffix, 'member_id' => $memberId]);
|
|
|
|
|
- DB::table('user_session')->insert([
|
|
|
|
|
- 'user_id' => 'login-' . $suffix, 'token' => 'token-' . $suffix,
|
|
|
|
|
- 'expire_time' => now()->timestamp + 86400,
|
|
|
|
|
- 'create_time' => now()->timestamp, 'update_time' => now()->timestamp,
|
|
|
|
|
- ]);
|
|
|
|
|
- }
|
|
|
|
|
- $this->router = new Router(new Dispatcher($this->app), $this->app);
|
|
|
|
|
- $this->app->instance('router', $this->router);
|
|
|
|
|
- $this->router->aliasMiddleware('check.token', CheckToken::class);
|
|
|
|
|
- $this->router->group(['prefix' => 'api'], function () {
|
|
|
|
|
- require dirname(__DIR__, 2) . '/routes/api.php';
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- protected function tearDown(): void
|
|
|
|
|
- {
|
|
|
|
|
- Carbon::setTestNow();
|
|
|
|
|
- DB::disconnect();
|
|
|
|
|
- Facade::clearResolvedInstances();
|
|
|
|
|
- parent::tearDown();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private function request(array $params = [], ?string $token = 'token-one'): JsonResponse
|
|
|
|
|
- {
|
|
|
|
|
- $request = Request::create('/api/rebate', 'GET', $params);
|
|
|
|
|
- if ($token !== null) $request->headers->set('Authorization', $token);
|
|
|
|
|
- $this->app->instance('request', $request);
|
|
|
|
|
- return $this->router->dispatch($request);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private function rebate(array $overrides = []): int
|
|
|
|
|
- {
|
|
|
|
|
- return DB::table('rebates')->insertGetId($overrides + [
|
|
|
|
|
- 'member_id' => '100001', 'date' => '2026-10-23',
|
|
|
|
|
- 'betting_amount' => '1000.00', 'effective_betting_amount' => '800.00',
|
|
|
|
|
- 'profit' => '-100.00', 'rebate_ratio' => '0.0050', 'amount' => '0.00', 'status' => 0,
|
|
|
|
|
- 'audited_by' => 'internal-admin', 'username' => 'historical-account', 'first_name' => '历史昵称',
|
|
|
|
|
- ]);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private function listing(array $params = [], string $token = 'token-one'): array
|
|
|
|
|
- {
|
|
|
|
|
- $response = $this->request($params, $token);
|
|
|
|
|
- $this->assertSame(200, $response->getStatusCode());
|
|
|
|
|
- $body = $response->getData(true);
|
|
|
|
|
- $this->assertSame(0, $body['code'], $body['msg']);
|
|
|
|
|
- return $body['data'];
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- public function test_route_requires_member_login_and_rejects_missing_invalid_expired_and_unlinked_sessions(): void
|
|
|
|
|
- {
|
|
|
|
|
- $this->rebate();
|
|
|
|
|
- $route = $this->router->getRoutes()->match(Request::create('/api/rebate', 'GET'));
|
|
|
|
|
- $this->assertSame(Rebate::class . '@index', $route->getAction('uses'));
|
|
|
|
|
- $this->assertContains('check.token', $route->middleware());
|
|
|
|
|
- $this->assertSame(['GET', 'HEAD'], $route->methods());
|
|
|
|
|
- foreach ([null, 'invalid', 'Bearer token-one', 'token-unlinked'] as $token) {
|
|
|
|
|
- $response = $this->request([], $token);
|
|
|
|
|
- $this->assertSame(401, $response->getStatusCode());
|
|
|
|
|
- $this->assertSame(401, $response->getData(true)['code']);
|
|
|
|
|
- }
|
|
|
|
|
- DB::table('user_session')->where('token', 'token-one')->update(['expire_time' => now()->timestamp]);
|
|
|
|
|
- $this->assertSame(401, $this->request()->getStatusCode());
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- public function test_user_cannot_select_another_members_rows_or_total_by_overriding_identity_parameters(): void
|
|
|
|
|
- {
|
|
|
|
|
- $own = $this->rebate();
|
|
|
|
|
- $other = $this->rebate(['member_id' => '200002', 'betting_amount' => 9000]);
|
|
|
|
|
- $before = DB::table('rebates')->orderBy('id')->get()->toJson();
|
|
|
|
|
- $list = $this->listing([
|
|
|
|
|
- 'member_id' => '200002', 'user_id' => 'login-two', 'id' => $other,
|
|
|
|
|
- 'username' => 'another-account', 'first_name' => '其他会员',
|
|
|
|
|
- 'user' => ['member_id' => '200002'],
|
|
|
|
|
- ]);
|
|
|
|
|
- $this->assertSame(1, $list['total']);
|
|
|
|
|
- $this->assertSame([$own], array_column($list['data'], 'id'));
|
|
|
|
|
- $this->assertSame(['100001'], array_column($list['data'], 'member_id'));
|
|
|
|
|
- $otherList = $this->listing([], 'token-two');
|
|
|
|
|
- $this->assertSame(1, $otherList['total']);
|
|
|
|
|
- $this->assertSame([$other], array_column($otherList['data'], 'id'));
|
|
|
|
|
- $this->assertSame($before, DB::table('rebates')->orderBy('id')->get()->toJson());
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- public function test_date_cutoff_uses_new_york_midnight_and_never_includes_today_or_future_rows(): void
|
|
|
|
|
- {
|
|
|
|
|
- $yesterday = $this->rebate();
|
|
|
|
|
- $today = $this->rebate(['date' => '2026-10-24']);
|
|
|
|
|
- $this->rebate(['date' => '2026-10-25']);
|
|
|
|
|
- $list = $this->listing();
|
|
|
|
|
- $this->assertSame(1, $list['total']);
|
|
|
|
|
- $this->assertSame([$yesterday], array_column($list['data'], 'id'));
|
|
|
|
|
- $this->assertSame(['total' => 0, 'data' => []], $this->listing(['date' => '2026-10-24']));
|
|
|
|
|
- Carbon::setTestNow(Carbon::parse('2026-10-25 00:00:00', 'America/New_York'));
|
|
|
|
|
- $list = $this->listing();
|
|
|
|
|
- $this->assertSame(2, $list['total']);
|
|
|
|
|
- $this->assertSame([$today, $yesterday], array_column($list['data'], 'id'));
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- public function test_filters_sorting_pagination_and_empty_results_use_the_same_member_scope(): void
|
|
|
|
|
- {
|
|
|
|
|
- $paid = $this->rebate(['status' => 1, 'betting_amount' => 9000]);
|
|
|
|
|
- $small = $this->rebate(['betting_amount' => 100]);
|
|
|
|
|
- $large = $this->rebate(['betting_amount' => 200]);
|
|
|
|
|
- $tied = $this->rebate(['betting_amount' => 200]);
|
|
|
|
|
- $older = $this->rebate(['date' => '2026-10-22', 'betting_amount' => 10000]);
|
|
|
|
|
- $this->rebate(['member_id' => '200002', 'status' => 1]);
|
|
|
|
|
- $all = $this->listing();
|
|
|
|
|
- $this->assertSame(5, $all['total']);
|
|
|
|
|
- $this->assertSame([$tied, $large, $small, $paid, $older], array_column($all['data'], 'id'));
|
|
|
|
|
- $unpaid = $this->listing(['status' => '0', 'date' => '2026-10-23']);
|
|
|
|
|
- $this->assertSame(3, $unpaid['total']);
|
|
|
|
|
- $this->assertSame([$tied, $large, $small], array_column($unpaid['data'], 'id'));
|
|
|
|
|
- $page = $this->listing(['status' => 0, 'limit' => 2, 'page' => 2]);
|
|
|
|
|
- $this->assertSame(4, $page['total']);
|
|
|
|
|
- $this->assertSame([$small, $older], array_column($page['data'], 'id'));
|
|
|
|
|
- $paidList = $this->listing(['status' => '1']);
|
|
|
|
|
- $this->assertSame(1, $paidList['total']);
|
|
|
|
|
- $this->assertSame([$paid], array_column($paidList['data'], 'id'));
|
|
|
|
|
- $this->assertSame(['total' => 5, 'data' => []], $this->listing(['page' => 99]));
|
|
|
|
|
- $this->assertSame(['total' => 0, 'data' => []], $this->listing(['date' => '2020-01-01']));
|
|
|
|
|
- $this->assertSame(5, $this->listing(['date' => null, 'status' => null, 'limit' => null, 'page' => null])['total']);
|
|
|
|
|
- $this->assertSame(5, $this->listing(['date' => '', 'status' => ''])['total']);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- public function test_list_returns_display_fields_and_saved_money_without_serializing_admin_or_dynamic_backflow_fields(): void
|
|
|
|
|
- {
|
|
|
|
|
- $this->rebate(['rebate_ratio' => null]);
|
|
|
|
|
- $grantTime = Carbon::parse('2026-10-24 10:00:00', 'Asia/Shanghai')->timestamp;
|
|
|
|
|
- $paid = $this->rebate(['status' => 1, 'amount' => '4.00', 'grant_time' => $grantTime]);
|
|
|
|
|
- $before = DB::table('rebates')->get()->toJson();
|
|
|
|
|
- $rows = collect($this->listing()['data'])->keyBy('id');
|
|
|
|
|
- $row = $rows[$paid];
|
|
|
|
|
- $this->assertSame([
|
|
|
|
|
- 'id', 'date', 'member_id', 'betting_amount', 'effective_betting_amount',
|
|
|
|
|
- 'profit', 'rebate_ratio', 'amount', 'status', 'grant_time',
|
|
|
|
|
- ], array_keys($row));
|
|
|
|
|
- $this->assertEquals(800, $row['effective_betting_amount']);
|
|
|
|
|
- $this->assertEquals(-100, $row['profit']);
|
|
|
|
|
- $this->assertEquals(0.005, $row['rebate_ratio']);
|
|
|
|
|
- $this->assertEquals(4, $row['amount']);
|
|
|
|
|
- $this->assertSame(date('Y-m-d H:i', $grantTime), $row['grant_time']);
|
|
|
|
|
- $this->assertSame('', $rows->first()['grant_time']);
|
|
|
|
|
- $this->assertNull($rows->first()['rebate_ratio']);
|
|
|
|
|
- $this->assertSame($before, DB::table('rebates')->get()->toJson());
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /** @dataProvider invalidFilters */
|
|
|
|
|
- public function test_invalid_filters_return_a_frontend_validation_error(array $params): void
|
|
|
|
|
- {
|
|
|
|
|
- $response = $this->request($params);
|
|
|
|
|
- $this->assertSame(200, $response->getStatusCode());
|
|
|
|
|
- $body = $response->getData(true);
|
|
|
|
|
- $this->assertSame(-1, $body['code']);
|
|
|
|
|
- $this->assertSame([], $body['data']);
|
|
|
|
|
- $this->assertNotEmpty($body['msg']);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- public static function invalidFilters(): array
|
|
|
|
|
- {
|
|
|
|
|
- return [
|
|
|
|
|
- 'page zero' => [['page' => 0]],
|
|
|
|
|
- 'invalid page' => [['page' => 'abc']],
|
|
|
|
|
- 'limit zero' => [['limit' => 0]],
|
|
|
|
|
- 'oversized limit' => [['limit' => 101]],
|
|
|
|
|
- 'invalid date' => [['date' => '2026-02-30']],
|
|
|
|
|
- 'invalid status' => [['status' => 2]],
|
|
|
|
|
- 'array status' => [['status' => [0, 1]]],
|
|
|
|
|
- ];
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|