|
|
@@ -55,7 +55,7 @@ class PromotionActivitiesTest extends TestCase
|
|
|
'reporting_activity' => 'id INTEGER PRIMARY KEY AUTOINCREMENT,user_id INTEGER,session_hash TEXT,observed_at TEXT',
|
|
|
'menus' => 'id INTEGER PRIMARY KEY AUTOINCREMENT,parent_id INTEGER,title TEXT,icon TEXT,uri TEXT,permission_name TEXT,sort INTEGER,status INTEGER,type INTEGER,created_at TEXT,updated_at TEXT',
|
|
|
] as $table => $columns) DB::statement('CREATE TABLE bot_' . $table . ' (' . $columns . ')');
|
|
|
- foreach (['2026_10_13_120000_add_activity_eligibility_fields.php', '2026_10_14_120000_add_activity_reward_quotas.php', '2026_10_25_120000_add_game_category_to_activity_rewards.php', '2026_10_26_160000_create_promotion_activities.php', '2026_10_27_130000_initialize_fixed_promotion_activities.php'] as $migration) (require dirname(__DIR__, 2) . '/database/migrations/' . $migration)->up();
|
|
|
+ foreach (['2026_10_13_120000_add_activity_eligibility_fields.php', '2026_10_14_120000_add_activity_reward_quotas.php', '2026_10_25_120000_add_game_category_to_activity_rewards.php', '2026_10_26_160000_create_promotion_activities.php', '2026_10_27_130000_initialize_fixed_promotion_activities.php', '2026_10_27_140000_add_promotion_activity_translations.php'] as $migration) (require dirname(__DIR__, 2) . '/database/migrations/' . $migration)->up();
|
|
|
$this->member(1); $this->member(2, 1);
|
|
|
DB::table('share_policies')->insert(['id' => 1, 'effective_date' => '2026-10-01', 'valid_member_min_deposit' => '200', 'valid_member_min_effective_bet' => '300']);
|
|
|
$this->service = new PromotionService();
|
|
|
@@ -70,11 +70,18 @@ class PromotionActivitiesTest extends TestCase
|
|
|
}
|
|
|
private function activity(int $type = 9, array $params = [], int $status = 1): int
|
|
|
{
|
|
|
- $input = ['title' => '测试活动', 'sub_title' => '规则', 'type' => $type, 'status' => $status,
|
|
|
+ $input = ['translations' => $this->texts(), 'type' => $type, 'status' => $status,
|
|
|
'start_time' => '2026-10-01 00:00:00', 'end_time' => '2026-12-31 23:59:59', 'lang' => 'zh', 'params' => $params];
|
|
|
if (in_array($type, [7, 9], true)) $input['id'] = (int) DB::table('promotion_activities')->where('type', $type)->value('id');
|
|
|
return (int) (new PromotionAdminService())->save($input)['id'];
|
|
|
}
|
|
|
+ private function texts(): array
|
|
|
+ {
|
|
|
+ return ['zh' => ['title' => '测试活动', 'sub_title' => '规则', 'content' => '<p>中文活动正文</p>'],
|
|
|
+ 'en' => ['title' => 'Test promotion', 'sub_title' => 'English subtitle', 'content' => '<p>English content</p>'],
|
|
|
+ 'vi' => ['title' => 'Khuyến mãi', 'sub_title' => 'Phụ đề', 'content' => '<p>Nội dung</p>']];
|
|
|
+ }
|
|
|
+
|
|
|
private function deposit(int $id, string $amount, ?string $time = null, string $type = '充值'): BalanceLog
|
|
|
{
|
|
|
return BalanceLog::create(['member_id' => 'm' . $id, 'type' => 1, 'amount' => $amount, 'change_type' => $type, 'created_at' => $time ?? PromotionFacts::now()]);
|
|
|
@@ -103,6 +110,117 @@ class PromotionActivitiesTest extends TestCase
|
|
|
$this->travel('2026-10-26 14:00:00'); return $this->service->progress($activity, 1, 'H5');
|
|
|
}
|
|
|
|
|
|
+ public function test_three_languages_edit_one_campaign_and_keep_rules_history_and_other_tabs(): void
|
|
|
+ {
|
|
|
+ $admin = new PromotionAdminService();
|
|
|
+ foreach ([7, 8, 9] as $type) {
|
|
|
+ $id = $this->activity($type); $before = $admin->detail($id);
|
|
|
+ $count = DB::table('promotion_activities')->count();
|
|
|
+ $input = $before; $input['translations']['en']['sub_title'] = 'Updated English subtitle';
|
|
|
+ $saved = $admin->save($input);
|
|
|
+ $this->assertSame($id, $saved['id']);
|
|
|
+ $this->assertSame($count, DB::table('promotion_activities')->count());
|
|
|
+ $this->assertSame($before['translations']['zh'], $saved['translations']['zh']);
|
|
|
+ $this->assertSame($before['translations']['vi'], $saved['translations']['vi']);
|
|
|
+ $this->assertSame('Updated English subtitle', $admin->detail($id)['translations']['en']['sub_title']);
|
|
|
+ foreach (['params', 'rewarded_count', 'budget_used', 'first_published_at'] as $field) $this->assertSame($before[$field], $saved[$field]);
|
|
|
+ foreach (['zh', 'en', 'vi'] as $lang) {
|
|
|
+ $rows = $this->service->listing(1, 'H5', $lang, $type)['data'];
|
|
|
+ $this->assertCount(1, $rows); $this->assertSame($id, $rows[0]['activity_id']);
|
|
|
+ $this->assertSame($saved['translations'][$lang]['sub_title'], $rows[0]['sub_title']);
|
|
|
+ $state = $this->service->detail($id, 1, 'H5', $lang);
|
|
|
+ $this->assertSame($lang, $state['lang']);
|
|
|
+ $this->assertSame($saved['translations'][$lang]['title'], $state['title']);
|
|
|
+ $this->assertSame($saved['translations'][$lang]['content'], $state['content']);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $this->assertSame(['zh', 'en', 'vi'], array_column($admin->options()['languages'], 'value'));
|
|
|
+ }
|
|
|
+
|
|
|
+ public function test_all_nine_text_fields_are_required_and_invalid_tabs_cannot_overwrite_saved_content(): void
|
|
|
+ {
|
|
|
+ $id = $this->activity(9); $admin = new PromotionAdminService(); $input = $admin->detail($id);
|
|
|
+ $before = DB::table('promotion_activities')->where('id', $id)->first();
|
|
|
+ foreach (['zh', 'en', 'vi'] as $lang) {
|
|
|
+ foreach (['title', 'sub_title', 'content'] as $field) {
|
|
|
+ $bad = $input; unset($bad['translations'][$lang][$field]);
|
|
|
+ try { $admin->save($bad); $this->fail('Missing language text was accepted'); }
|
|
|
+ catch (ValidationException $e) { $this->assertTrue($e->validator->errors()->has('translations.' . $lang . '.' . $field)); }
|
|
|
+ $bad = $input; $bad['translations'][$lang][$field] = '<p> <br></p>';
|
|
|
+ $this->invalid(fn () => $admin->save($bad), '不能为空');
|
|
|
+ }
|
|
|
+ $bad = $input; unset($bad['translations'][$lang]);
|
|
|
+ $this->invalid(fn () => $admin->save($bad), '完整填写');
|
|
|
+ }
|
|
|
+ $bad = $input; unset($bad['translations']); $bad['title'] = '旧单语言表单';
|
|
|
+ $this->invalid(fn () => $admin->save($bad), '完整填写');
|
|
|
+ $bad = $input; $bad['translations']['fr'] = $input['translations']['en'];
|
|
|
+ try { $admin->save($bad); $this->fail('Unknown language was accepted'); }
|
|
|
+ catch (ValidationException $e) { $this->assertTrue($e->validator->errors()->has('translations')); }
|
|
|
+ $bad = $input; $bad['translations']['en']['sub_title'] = str_repeat('a', 201);
|
|
|
+ try { $admin->save($bad); $this->fail('Oversized subtitle was accepted'); }
|
|
|
+ catch (ValidationException $e) { $this->assertTrue($e->validator->errors()->has('translations.en.sub_title')); }
|
|
|
+ $this->assertEquals($before, DB::table('promotion_activities')->where('id', $id)->first());
|
|
|
+ }
|
|
|
+
|
|
|
+ public function test_admin_http_save_returns_all_tabs_and_rejects_partial_language_payloads(): void
|
|
|
+ {
|
|
|
+ $controller = new \App\Http\Controllers\admin\Promotion(); $admin = new PromotionAdminService();
|
|
|
+ $input = ['type' => 8, 'status' => 0, 'start_time' => '2026-10-26', 'end_time' => '2026-12-31', 'translations' => $this->texts()];
|
|
|
+ $this->app->instance('request', Request::create('/admin/promotion/submit', 'POST', $input));
|
|
|
+ $saved = $controller->submit($admin)->getData(true);
|
|
|
+ $this->assertSame(0, $saved['code']); $this->assertSame($this->texts(), $saved['data']['translations']);
|
|
|
+ $input['id'] = $saved['data']['id']; unset($input['translations']['en']);
|
|
|
+ $this->app->instance('request', Request::create('/admin/promotion/submit', 'POST', $input));
|
|
|
+ $failed = $controller->submit($admin)->getData(true);
|
|
|
+ $this->assertSame(-3, $failed['code']); $this->assertStringContainsString('英文', $failed['msg']);
|
|
|
+ $this->assertSame($this->texts(), $admin->detail($input['id'])['translations']);
|
|
|
+ }
|
|
|
+
|
|
|
+ public function test_incomplete_legacy_activity_can_be_hidden_but_cannot_be_published_without_all_tabs(): void
|
|
|
+ {
|
|
|
+ $id = (int) DB::table('promotion_activities')->where('type', 7)->value('id');
|
|
|
+ $admin = new PromotionAdminService();
|
|
|
+ $this->assertSame(0, (int) $admin->status($id, 0)['status']);
|
|
|
+ $this->invalid(fn () => $admin->status($id, 1), '不能为空');
|
|
|
+ $this->assertSame(0, (int) DB::table('promotion_activities')->where('id', $id)->value('status'));
|
|
|
+ $this->assertNull(DB::table('promotion_campaigns')->where('activity_id', $id)->value('first_published_at'));
|
|
|
+ }
|
|
|
+
|
|
|
+ public function test_first_deposit_translation_does_not_split_or_duplicate_awards(): void
|
|
|
+ {
|
|
|
+ $id = $this->activity(8, ['min_deposit' => 100, 'reward_amount' => 25]);
|
|
|
+ DB::table('users')->where('id', 1)->update(['language' => 'vi']);
|
|
|
+ $deposit = $this->deposit(1, '100'); $this->service->onDeposit($deposit);
|
|
|
+ $this->assertSame(1, DB::table('promotion_rewards')->where('activity_id', $id)->count());
|
|
|
+ DB::table('users')->where('id', 1)->update(['language' => 'en']);
|
|
|
+ $this->service->onDeposit($deposit);
|
|
|
+ $this->assertSame(1, DB::table('promotion_rewards')->where('activity_id', $id)->count());
|
|
|
+ $this->assertSame('25.0000000000', (string) DB::table('wallets')->where('member_id', 'm1')->value('available_balance'));
|
|
|
+ $this->assertSame(1, DB::table('promotion_accounts')->where('activity_id', $id)->count());
|
|
|
+ }
|
|
|
+
|
|
|
+ public function test_translation_migration_preserves_legacy_texts_and_campaign_identity(): void
|
|
|
+ {
|
|
|
+ $migration = require dirname(__DIR__, 2) . '/database/migrations/2026_10_27_140000_add_promotion_activity_translations.php';
|
|
|
+ $migration->down();
|
|
|
+ $id = DB::table('promotion_activities')->where('type', 7)->value('id');
|
|
|
+ DB::table('promotion_activities')->where('id', $id)->update(['lang' => 'en', 'title' => 'Existing title', 'sub_title' => 'Existing subtitle', 'content' => '<p>Existing content</p>']);
|
|
|
+ $rows = DB::table('promotion_activities')->orderBy('id')->get();
|
|
|
+ $published = DB::table('promotion_campaigns')->orderBy('activity_id')->get();
|
|
|
+ $migration->up();
|
|
|
+ foreach ($rows as $row) {
|
|
|
+ $saved = DB::table('promotion_activities')->where('id', $row->id)->first();
|
|
|
+ foreach ((array) $row as $field => $value) $this->assertSame($value, $saved->$field);
|
|
|
+ }
|
|
|
+ $texts = (new PromotionAdminService())->detail((int) $id)['translations'];
|
|
|
+ $this->assertSame(['title' => 'Existing title', 'sub_title' => 'Existing subtitle', 'content' => '<p>Existing content</p>'], $texts['en']);
|
|
|
+ $this->assertSame('', $texts['zh']['title']);
|
|
|
+ $this->assertEquals($published, DB::table('promotion_campaigns')->orderBy('activity_id')->get());
|
|
|
+ $this->assertSame(0, DB::table('menus')->count());
|
|
|
+ $this->assertSame(0, $this->service->listing(1, 'H5', 'vi', 7)['total']);
|
|
|
+ }
|
|
|
+
|
|
|
public function test_probability_default_zero_boundaries_and_incomplete_gate(): void
|
|
|
{
|
|
|
$this->assertSame('20.00', PromotionRules::normalize(9, [])['completion_probability']);
|
|
|
@@ -270,6 +388,20 @@ class PromotionActivitiesTest extends TestCase
|
|
|
$this->assertTrue(DB::connection()->getSchemaBuilder()->hasTable('promotion_activities'));
|
|
|
}
|
|
|
|
|
|
+ public function test_admin_http_amounts_are_compact_without_changing_saved_rules_or_counters(): void
|
|
|
+ {
|
|
|
+ $id = $this->activity(8, ['min_deposit' => '200', 'reward_amount' => '10.12']);
|
|
|
+ $request = Request::create('/admin/promotion/detail', 'GET', ['id' => $id]); $this->app->instance('request', $request);
|
|
|
+ $response = (new \App\Http\Controllers\admin\Promotion())->detail(new PromotionAdminService())->getData(true);
|
|
|
+ $this->assertSame(0, $response['code']);
|
|
|
+ $this->assertSame('200', $response['data']['params']['min_deposit']);
|
|
|
+ $this->assertSame('10.12', $response['data']['params']['reward_amount']);
|
|
|
+ $this->assertSame($id, $response['data']['id']);
|
|
|
+ $saved = json_decode(DB::table('promotion_activities')->where('id', $id)->value('params'), true);
|
|
|
+ $this->assertSame('200.00', $saved['min_deposit']);
|
|
|
+ $this->assertSame('10.12', $saved['reward_amount']);
|
|
|
+ }
|
|
|
+
|
|
|
public function test_fixed_activities_are_initialized_disabled_without_publishing_or_creating_menus(): void
|
|
|
{
|
|
|
$this->assertSame(2, DB::table('promotion_activities')->count());
|
|
|
@@ -288,7 +420,7 @@ class PromotionActivitiesTest extends TestCase
|
|
|
{
|
|
|
$admin = new PromotionAdminService();
|
|
|
foreach ([7, 9] as $type) {
|
|
|
- $input = ['title' => '不能新建', 'type' => $type, 'status' => 0,
|
|
|
+ $input = ['translations' => $this->texts(), 'type' => $type, 'status' => 0,
|
|
|
'start_time' => '2026-10-01 00:00:00', 'end_time' => '2026-12-31 23:59:59'];
|
|
|
$this->invalid(fn () => $admin->save($input), '不支持新增');
|
|
|
$this->assertSame(1, DB::table('promotion_activities')->where('type', $type)->count());
|
|
|
@@ -305,7 +437,7 @@ class PromotionActivitiesTest extends TestCase
|
|
|
$first = $this->activity(8); $second = $this->activity(8);
|
|
|
$this->assertNotSame($first, $second);
|
|
|
$this->assertSame(2, DB::table('promotion_activities')->where('type', 8)->count());
|
|
|
- $this->invalid(fn () => (new PromotionAdminService())->save(['id' => $first, 'title' => '不能换类型', 'type' => 9, 'status' => 0,
|
|
|
+ $this->invalid(fn () => (new PromotionAdminService())->save(['id' => $first, 'translations' => $this->texts(), 'type' => 9, 'status' => 0,
|
|
|
'start_time' => '2026-10-01 00:00:00', 'end_time' => '2026-12-31 23:59:59']), '不能变更活动类型');
|
|
|
$this->assertSame(8, (int) DB::table('promotion_activities')->where('id', $first)->value('type'));
|
|
|
}
|
|
|
@@ -328,7 +460,7 @@ class PromotionActivitiesTest extends TestCase
|
|
|
public function test_admin_partial_rule_edit_preserves_other_settings_and_zero_probability(): void
|
|
|
{
|
|
|
$id = $this->activity(9, ['turnover_ratio' => 3, 'deposit_amount' => 5000]);
|
|
|
- $saved = (new PromotionAdminService())->save(['id' => $id, 'title' => '独立转盘', 'type' => 9, 'status' => 1,
|
|
|
+ $saved = (new PromotionAdminService())->save(['id' => $id, 'translations' => $this->texts(), 'type' => 9, 'status' => 1,
|
|
|
'start_time' => '2026-10-01', 'end_time' => '2026-12-31', 'params' => ['completion_probability' => 0]]);
|
|
|
$this->assertSame('0.00', $saved['params']['completion_probability']); $this->assertSame('3.00', $saved['params']['turnover_ratio']);
|
|
|
$this->assertSame('5000.00', $saved['params']['deposit_amount']); $this->assertSame(0, DB::table('activity_rewards')->count());
|