| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158 |
- <?php
- namespace Tests\Integration;
- use App\Http\Controllers\admin\Promotion;
- use App\Services\PromotionAdminService;
- use Illuminate\Config\Repository;
- use Illuminate\Database\Capsule\Manager;
- use Illuminate\Foundation\Application;
- use Illuminate\Http\Request;
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Facade;
- use PHPUnit\Framework\TestCase;
- class PromotionRecordDisplayTest extends TestCase
- {
- private Application $app;
- protected function setUp(): void
- {
- Facade::clearResolvedInstances();
- $this->app = new Application(dirname(__DIR__, 2));
- $this->app->instance('config', new Repository(['app' => ['locale' => 'zh']]));
- $this->app->instance('log', new \Psr\Log\NullLogger());
- $translator = new \Illuminate\Translation\Translator(new \Illuminate\Translation\ArrayLoader(), 'zh');
- $this->app->instance('translator', $translator);
- $this->app->instance('validator', new \Illuminate\Validation\Factory($translator, $this->app));
- $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new \Tests\Support\JsonResponseFactory());
- 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());
- foreach ([
- 'users' => 'id INTEGER PRIMARY KEY,account TEXT',
- 'promotion_activities' => 'id INTEGER PRIMARY KEY,type INTEGER',
- 'promotion_accounts' => 'id INTEGER PRIMARY KEY,activity_id INTEGER,user_id INTEGER,round_no INTEGER,spins INTEGER,applied_at TEXT,application_snapshot TEXT,created_at TEXT,updated_at TEXT',
- 'promotion_rewards' => 'id INTEGER PRIMARY KEY,activity_id INTEGER,user_id INTEGER,account_id INTEGER,round_no INTEGER,amount TEXT,required_flow TEXT,status TEXT,credited_log_id INTEGER,rule_snapshot TEXT',
- 'promotion_coupons' => 'id INTEGER PRIMARY KEY,reward_id INTEGER,activity_id INTEGER,user_id INTEGER,member_id TEXT,role TEXT,inviter_user_id INTEGER,min_amount TEXT,discount TEXT,categories TEXT,status TEXT,valid_from TEXT,valid_until TEXT',
- ] as $table => $columns) DB::statement('CREATE TABLE bot_' . $table . ' (' . $columns . ')');
- foreach ([164, 165, 168, 169, 170, 171] as $id) DB::table('users')->insert(['id' => $id, 'account' => 'member' . $id]);
- DB::table('promotion_activities')->insert(['id' => 1, 'type' => 7]);
- DB::table('promotion_accounts')->insert(['id' => 11865, 'activity_id' => 1, 'user_id' => 164, 'round_no' => 1, 'spins' => 0,
- 'applied_at' => '2026-10-29 11:52:42', 'application_snapshot' => json_encode(['rules' => ['reward_amount' => '500']])]);
- DB::table('promotion_rewards')->insert(['id' => 3, 'activity_id' => 1, 'user_id' => 164, 'account_id' => 11865, 'round_no' => 1,
- 'amount' => '500.00', 'required_flow' => '0.00', 'status' => 'paid', 'credited_log_id' => 20800,
- 'rule_snapshot' => json_encode(['invite_count' => 3, 'reward_amount' => '500', 'qualified_invitees' => [168, 169, 170]])]);
- }
- protected function tearDown(): void
- {
- DB::disconnect(); Facade::clearResolvedInstances(); parent::tearDown();
- }
- private function records(string $kind, array $filters = []): array
- {
- $this->app->instance('request', Request::create('/admin/promotion/records', 'GET', $filters + ['activity_id' => 1, 'kind' => $kind]));
- $body = (new Promotion())->records(new PromotionAdminService())->getData(true);
- $this->assertSame(0, $body['code'], $body['msg']);
- return $body['data'];
- }
- private function coupon(int $id, int $user, string $role, string $min = '100', string $discount = '50', int $reward = 3, string $status = 'unused'): void
- {
- DB::table('promotion_coupons')->insert(['id' => $id, 'reward_id' => $reward, 'activity_id' => 1, 'user_id' => $user, 'member_id' => 'wallet-' . $user,
- 'role' => $role, 'inviter_user_id' => 164, 'min_amount' => $min, 'discount' => $discount, 'categories' => '["self:sport"]', 'status' => $status,
- 'valid_from' => '2000-01-01 00:00:00', 'valid_until' => '2001-01-01 00:00:00']);
- }
- public function test_legacy_cash_reward_exposes_the_three_qualified_members_without_inventing_coupon_awards(): void
- {
- DB::table('users')->where('id', 168)->update(['account' => '000168']);
- $before = DB::table('promotion_rewards')->first();
- $result = $this->records('rewards', ['user_id' => 164]);
- $row = $result['data'][0];
- $this->assertSame(1, $result['total']);
- $this->assertSame(3, $row['id']);
- $this->assertSame('cash', $row['reward_mode']);
- $this->assertSame(['id' => 164, 'account' => 'member164', 'coupons' => []], $row['inviter']);
- $this->assertSame([168, 169, 170], array_column($row['invitees'], 'id'));
- $this->assertSame(['000168', 'member169', 'member170'], array_column($row['invitees'], 'account'));
- $this->assertSame([[], [], []], array_column($row['invitees'], 'coupons'));
- $this->assertSame('500', $row['amount']);
- $this->assertSame((array)$before, (array)DB::table('promotion_rewards')->first());
- $this->assertSame(0, DB::table('promotion_coupons')->count());
- }
- public function test_coupon_reward_groups_actual_inviter_and_invitee_awards_including_used_and_expired_coupons(): void
- {
- DB::table('promotion_rewards')->where('id', 3)->update(['credited_log_id' => null, 'rule_snapshot' => json_encode([
- 'reward_mode' => 'coupon', 'qualified_invitees' => [168, 169, 170, 171], 'coupon_invitees' => [168, 169, 170],
- ])]);
- $this->coupon(1, 164, 'inviter'); $this->coupon(2, 164, 'inviter', status: 'used');
- $this->coupon(3, 164, 'inviter', '200', '100');
- foreach ([168, 169, 170] as $id) $this->coupon($id, $id, 'invitee', '100', '100');
- $this->coupon(999, 168, 'invitee', '999', '999', reward: 999); // Another reward must not bleed into this row.
- DB::table('users')->where('id', 169)->delete();
- DB::table('users')->where('id', 170)->update(['account' => '']);
- $before = DB::table('promotion_coupons')->orderBy('id')->get()->toJson();
- foreach (['rewards', 'accounts'] as $kind) {
- $row = $this->records($kind)['data'][0];
- $this->assertSame('coupon', $row['reward_mode']);
- $this->assertSame([['min_amount' => '100', 'discount' => '50', 'quantity' => 2], ['min_amount' => '200', 'discount' => '100', 'quantity' => 1]], $row['inviter']['coupons']);
- $this->assertSame([168, 169, 170], array_column($row['invitees'], 'id'));
- $this->assertSame(['member168', null, null], array_column($row['invitees'], 'account'));
- foreach ($row['invitees'] as $person) $this->assertSame([['min_amount' => '100', 'discount' => '100', 'quantity' => 1]], $person['coupons']);
- if ($kind === 'accounts') {
- $this->assertSame(3, $row['reward_id']);
- $this->assertSame('paid', $row['reward_status']);
- }
- }
- $this->assertSame($before, DB::table('promotion_coupons')->orderBy('id')->get()->toJson());
- }
- public function test_application_without_reward_has_no_award_list_and_uses_its_application_mode(): void
- {
- DB::table('promotion_accounts')->insert(['id' => 11866, 'activity_id' => 1, 'user_id' => 165,
- 'application_snapshot' => '{"rules":{"reward_mode":"coupon"}}']);
- $row = $this->records('accounts', ['user_id' => 165])['data'][0];
- $this->assertNull($row['reward_id']); $this->assertNull($row['reward_status']); $this->assertSame('coupon', $row['reward_mode']);
- $this->assertSame(['id' => 165, 'account' => 'member165', 'coupons' => []], $row['inviter']);
- $this->assertSame([], $row['invitees']);
- }
- public function test_pagination_still_counts_rewards_not_nested_people_and_does_not_mix_reward_snapshots(): void
- {
- DB::table('promotion_rewards')->insert(['id' => 4, 'activity_id' => 1, 'user_id' => 164, 'account_id' => 11865, 'round_no' => 2,
- 'amount' => '5', 'status' => 'paid', 'rule_snapshot' => '{"qualified_invitees":[171]}' ]);
- $first = $this->records('rewards', ['limit' => 1, 'page' => 1]);
- $second = $this->records('rewards', ['limit' => 1, 'page' => 2]);
- $this->assertSame(2, $first['total']); $this->assertSame(2, $second['total']);
- $this->assertSame([171], array_column($first['data'][0]['invitees'], 'id'));
- $this->assertSame([168, 169, 170], array_column($second['data'][0]['invitees'], 'id'));
- }
- public function test_cash_history_does_not_require_coupon_tables_and_bad_snapshot_does_not_break_the_list(): void
- {
- DB::statement('DROP TABLE bot_promotion_coupons');
- $this->assertCount(3, $this->records('rewards')['data'][0]['invitees']);
- DB::table('promotion_rewards')->update(['rule_snapshot' => 'invalid historical snapshot']);
- $row = $this->records('rewards')['data'][0];
- $this->assertSame('cash', $row['reward_mode']); $this->assertSame([], $row['invitees']);
- }
- public function test_coupon_record_list_also_contains_holder_and_inviter_accounts(): void
- {
- $this->coupon(1, 168, 'invitee');
- $row = $this->records('coupons', ['user_id' => 164])['data'][0];
- $this->assertSame(168, $row['user_id']); $this->assertSame('member168', $row['account']);
- $this->assertSame(164, $row['inviter_user_id']); $this->assertSame('member164', $row['inviter_account']);
- }
- public function test_unrelated_activity_types_keep_the_existing_record_shape(): void
- {
- DB::table('promotion_activities')->update(['type' => 9]);
- $row = $this->records('rewards')['data'][0];
- $this->assertArrayNotHasKey('inviter', $row); $this->assertArrayNotHasKey('invitees', $row);
- }
- }
|