|
|
@@ -128,12 +128,14 @@ class BannerPublicationTest extends TestCase
|
|
|
return collect(BannerVisibilityService::decorate(Banner::query()->orderBy('id')->get(), $now))->keyBy('id')->all();
|
|
|
}
|
|
|
|
|
|
- private function assertState(array $row, int $manual, int $effective, ?string $reason, ?string $activityId): void
|
|
|
+ private function assertState(array $row, ?int $manual, int $effective, ?string $reason, ?string $activityId): void
|
|
|
{
|
|
|
- $this->assertSame($manual, $row['status']);
|
|
|
+ $this->assertSame($manual, $row['manual_status']);
|
|
|
+ $this->assertSame($effective, $row['status']);
|
|
|
$this->assertSame($effective, $row['effective_status']);
|
|
|
$this->assertSame($reason, $row['offline_reason']);
|
|
|
$this->assertSame($activityId, $row['activity_id']);
|
|
|
+ $this->assertSame($activityId === null, $row['can_manual_toggle']);
|
|
|
}
|
|
|
|
|
|
public function test_migration_publishes_existing_banners_by_default_and_rolls_back_only_its_column(): void
|
|
|
@@ -178,17 +180,44 @@ class BannerPublicationTest extends TestCase
|
|
|
$this->assertSame('https://banner.example.test/banner.png', $banner->image);
|
|
|
}
|
|
|
|
|
|
- public function test_status_endpoint_accepts_repeated_zero_and_one_without_changing_banner_content(): void
|
|
|
+ public function test_status_endpoint_accepts_both_aliases_and_repeated_values_without_changing_banner_content(): void
|
|
|
{
|
|
|
$id = $this->banner(['sort' => 19, 'lang' => 'en', 'link' => 'https://example.test']);
|
|
|
- foreach ([0, 0, 1, 1, '0', '1'] as $status) {
|
|
|
- $result = $this->status(['id' => $id, 'status' => $status]);
|
|
|
+ foreach (['status', 'manual_status'] as $field) {
|
|
|
+ foreach ([0, 0, 1, 1, '0', '1'] as $status) {
|
|
|
+ $result = $this->status(['id' => $id, $field => $status]);
|
|
|
+ $this->assertSame(HttpStatus::OK, $result['code'], $result['msg']);
|
|
|
+ $row = DB::table('banner')->find($id);
|
|
|
+ $this->assertSame((int) $status, (int) $row->status);
|
|
|
+ $this->assertSame('https://example.test', $row->link);
|
|
|
+ $this->assertSame(19, (int) $row->sort);
|
|
|
+ $this->assertSame('en', $row->lang);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public function test_manual_status_takes_precedence_in_full_forms_and_status_requests(): void
|
|
|
+ {
|
|
|
+ foreach ([
|
|
|
+ [['manual_status' => 0], 0],
|
|
|
+ [['status' => 1], 1],
|
|
|
+ [['manual_status' => null], 1],
|
|
|
+ [['status' => 1, 'manual_status' => null], 1],
|
|
|
+ [['status' => 0, 'manual_status' => 1], 1],
|
|
|
+ [['status' => 1, 'manual_status' => 0], 0],
|
|
|
+ ] as [$fields, $expected]) {
|
|
|
+ $result = $this->save($fields);
|
|
|
$this->assertSame(HttpStatus::OK, $result['code'], $result['msg']);
|
|
|
- $row = DB::table('banner')->find($id);
|
|
|
- $this->assertSame((int) $status, (int) $row->status);
|
|
|
- $this->assertSame('https://example.test', $row->link);
|
|
|
- $this->assertSame(19, (int) $row->sort);
|
|
|
- $this->assertSame('en', $row->lang);
|
|
|
+ $id = DB::table('banner')->latest('id')->value('id');
|
|
|
+ $this->assertSame($expected, (int) DB::table('banner')->where('id', $id)->value('status'));
|
|
|
+ }
|
|
|
+ foreach ([[0, 1], [1, 0]] as [$legacy, $manual]) {
|
|
|
+ $result = $this->status(['id' => $id, 'status' => $legacy, 'manual_status' => $manual]);
|
|
|
+ $this->assertSame(HttpStatus::OK, $result['code'], $result['msg']);
|
|
|
+ $this->assertSame($manual, (int) DB::table('banner')->where('id', $id)->value('status'));
|
|
|
+ $result = $this->save(['id' => $id, 'title' => '兼容完整编辑', 'status' => $legacy, 'manual_status' => $manual]);
|
|
|
+ $this->assertSame(HttpStatus::OK, $result['code'], $result['msg']);
|
|
|
+ $this->assertSame($manual, (int) DB::table('banner')->where('id', $id)->value('status'));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -198,13 +227,18 @@ class BannerPublicationTest extends TestCase
|
|
|
$this->assertSame(HttpStatus::OK, $result['code'], $result['msg']);
|
|
|
$id = DB::table('banner')->value('id');
|
|
|
$this->assertSame(0, (int) DB::table('banner')->where('id', $id)->value('status'));
|
|
|
- foreach ([null, -1, 2, 'invalid'] as $status) {
|
|
|
- $result = $this->save(['id' => $id, 'title' => '不应保存', 'status' => $status]);
|
|
|
- $this->assertSame(HttpStatus::VALIDATION_FAILED, $result['code']);
|
|
|
- $row = DB::table('banner')->find($id);
|
|
|
- $this->assertSame(0, (int) $row->status);
|
|
|
- $this->assertSame('轮播图', $row->title);
|
|
|
+ foreach (['status', 'manual_status'] as $field) {
|
|
|
+ foreach ($field === 'status' ? [null, -1, 2, 'invalid'] : [-1, 2, 'invalid'] as $status) {
|
|
|
+ $result = $this->save(['id' => $id, 'title' => '不应保存', $field => $status]);
|
|
|
+ $this->assertSame(HttpStatus::VALIDATION_FAILED, $result['code']);
|
|
|
+ $row = DB::table('banner')->find($id);
|
|
|
+ $this->assertSame(0, (int) $row->status);
|
|
|
+ $this->assertSame('轮播图', $row->title);
|
|
|
+ }
|
|
|
}
|
|
|
+ $result = $this->save(['id' => $id, 'manual_status' => null, 'title' => '空值保持手动设置']);
|
|
|
+ $this->assertSame(HttpStatus::OK, $result['code'], $result['msg']);
|
|
|
+ $this->assertSame(0, (int) DB::table('banner')->where('id', $id)->value('status'));
|
|
|
$this->assertSame(1, DB::table('banner')->count());
|
|
|
}
|
|
|
|
|
|
@@ -214,7 +248,9 @@ class BannerPublicationTest extends TestCase
|
|
|
foreach ([[], ['id' => $id], ['status' => 0], ['id' => $id, 'status' => -1],
|
|
|
['id' => $id, 'status' => 2], ['id' => $id, 'status' => 'invalid'],
|
|
|
['id' => $id, 'status' => null], ['id' => 0, 'status' => 0],
|
|
|
- ['id' => 'invalid', 'status' => 0]] as $params) {
|
|
|
+ ['id' => 'invalid', 'status' => 0], ['manual_status' => 0],
|
|
|
+ ['id' => $id, 'manual_status' => null], ['id' => $id, 'manual_status' => -1],
|
|
|
+ ['id' => $id, 'manual_status' => 2], ['id' => $id, 'manual_status' => 'invalid']] as $params) {
|
|
|
$this->assertSame(HttpStatus::VALIDATION_FAILED, $this->status($params)['code']);
|
|
|
$this->assertSame(1, (int) DB::table('banner')->where('id', $id)->value('status'));
|
|
|
}
|
|
|
@@ -244,7 +280,7 @@ class BannerPublicationTest extends TestCase
|
|
|
$expected[$id] = ['activity_missing', '999'];
|
|
|
$rows = $this->decorated($now);
|
|
|
foreach ($expected as $id => [$reason, $activityId]) {
|
|
|
- $this->assertState($rows[$id], 1, $reason === null ? 1 : 0, $reason, $activityId);
|
|
|
+ $this->assertState($rows[$id], null, $reason === null ? 1 : 0, $reason, $activityId);
|
|
|
$this->assertSame(1, (int) DB::table('banner')->where('id', $id)->value('status'));
|
|
|
}
|
|
|
}
|
|
|
@@ -268,27 +304,134 @@ class BannerPublicationTest extends TestCase
|
|
|
$this->assertCount(1, $queries, 'Unbound banners should not query activities.');
|
|
|
}
|
|
|
|
|
|
- public function test_manual_on_cannot_override_ended_activity_and_reopening_respects_manual_off(): void
|
|
|
+ public function test_bound_banners_ignore_archived_manual_status_across_activity_states(): void
|
|
|
{
|
|
|
$now = time();
|
|
|
$this->activity(7, $now - 100, $now - 10);
|
|
|
- $on = $this->banner(['link' => '7', 'status' => 0]);
|
|
|
+ $on = $this->banner(['link' => '7', 'status' => 1]);
|
|
|
$off = $this->banner(['link' => '7', 'status' => 0]);
|
|
|
- $this->assertSame(HttpStatus::OK, $this->status(['id' => $on, 'status' => 1])['code']);
|
|
|
$rows = $this->decorated($now);
|
|
|
- $this->assertState($rows[$on], 1, 0, 'activity_ended', '7');
|
|
|
- $this->assertState($rows[$off], 0, 0, 'manual', '7');
|
|
|
+ $this->assertState($rows[$on], null, 0, 'activity_ended', '7');
|
|
|
+ $this->assertState($rows[$off], null, 0, 'activity_ended', '7');
|
|
|
DB::table('activity_rewards')->where('id', 7)->update(['end_time' => $now + 100]);
|
|
|
$rows = $this->decorated($now);
|
|
|
- $this->assertState($rows[$on], 1, 1, null, '7');
|
|
|
- $this->assertState($rows[$off], 0, 0, 'manual', '7');
|
|
|
+ $this->assertState($rows[$on], null, 1, null, '7');
|
|
|
+ $this->assertState($rows[$off], null, 1, null, '7');
|
|
|
DB::table('activity_rewards')->where('id', 7)->update(['status' => 0]);
|
|
|
- $this->assertState($this->decorated($now)[$on], 1, 0, 'activity_offline', '7');
|
|
|
+ $this->assertState($this->decorated($now)[$on], null, 0, 'activity_offline', '7');
|
|
|
DB::table('activity_rewards')->where('id', 7)->update(['status' => 1]);
|
|
|
- $this->assertState($this->decorated($now)[$on], 1, 1, null, '7');
|
|
|
+ $this->assertState($this->decorated($now)[$on], null, 1, null, '7');
|
|
|
+ $this->assertSame(1, (int) DB::table('banner')->where('id', $on)->value('status'));
|
|
|
+ $this->assertSame(0, (int) DB::table('banner')->where('id', $off)->value('status'));
|
|
|
+ }
|
|
|
+
|
|
|
+ public function test_get_expired_banner_reports_actual_off_and_full_row_edit_preserves_archive_for_reopening(): void
|
|
|
+ {
|
|
|
+ $now = time();
|
|
|
+ $this->activity(7, $now - 120, $now - 60);
|
|
|
+ $on = $this->banner(['link' => '7', 'status' => 1, 'sort' => 17, 'lang' => 'en']);
|
|
|
+ $off = $this->banner(['link' => '7', 'status' => 0]);
|
|
|
+ $manualOff = $this->banner(['link' => 'https://example.test', 'status' => 0]);
|
|
|
+ $page = $this->listing();
|
|
|
+ $rows = collect($page['data'])->keyBy('id')->all();
|
|
|
+ $this->assertSame(3, $page['total']);
|
|
|
+ $this->assertState($rows[$on], null, 0, 'activity_ended', '7');
|
|
|
+ $this->assertState($rows[$off], null, 0, 'activity_ended', '7');
|
|
|
+ $this->assertState($rows[$manualOff], 0, 0, 'manual', null);
|
|
|
+ $this->assertSame(1, (int) DB::table('banner')->where('id', $on)->value('status'));
|
|
|
+
|
|
|
+ // Resubmitted activity-derived status and null manual_status do not alter the archived database flag.
|
|
|
+ $edited = $rows[$on];
|
|
|
+ $edited['title'] = '过期活动轮播图编辑';
|
|
|
+ $result = $this->save($edited);
|
|
|
+ $this->assertSame(HttpStatus::OK, $result['code'], $result['msg']);
|
|
|
+ $stored = DB::table('banner')->find($on);
|
|
|
+ $this->assertSame(1, (int) $stored->status);
|
|
|
+ $this->assertSame('过期活动轮播图编辑', $stored->title);
|
|
|
+ $this->assertSame('7', $stored->link);
|
|
|
+ $this->assertSame(17, (int) $stored->sort);
|
|
|
+ $this->assertSame('en', $stored->lang);
|
|
|
+ $this->assertSame('/banner.png', $stored->image);
|
|
|
+ $rows = collect($this->listing()['data'])->keyBy('id')->all();
|
|
|
+ $this->assertState($rows[$on], null, 0, 'activity_ended', '7');
|
|
|
+
|
|
|
+ DB::table('activity_rewards')->where('id', 7)->update(['end_time' => time() + 600]);
|
|
|
+ $rows = collect($this->listing()['data'])->keyBy('id')->all();
|
|
|
+ $this->assertState($rows[$on], null, 1, null, '7');
|
|
|
+ $this->assertState($rows[$off], null, 1, null, '7');
|
|
|
+ $this->assertState($rows[$manualOff], 0, 0, 'manual', null);
|
|
|
+ $this->assertSame(1, (int) DB::table('banner')->where('id', $on)->value('status'));
|
|
|
$this->assertSame(0, (int) DB::table('banner')->where('id', $off)->value('status'));
|
|
|
}
|
|
|
|
|
|
+ public function test_bound_banners_reject_both_manual_toggle_aliases_without_database_mutation(): void
|
|
|
+ {
|
|
|
+ $now = time();
|
|
|
+ $this->activity(7, $now - 120, $now + 600);
|
|
|
+ $this->activity(8, $now - 120, $now - 60);
|
|
|
+ foreach ([['7', 0], ['8', 1]] as [$link, $archivedStatus]) {
|
|
|
+ $id = $this->banner(['link' => $link, 'status' => $archivedStatus]);
|
|
|
+ $before = (array) DB::table('banner')->find($id);
|
|
|
+ foreach (['status', 'manual_status'] as $field) {
|
|
|
+ foreach ([0, 1] as $value) {
|
|
|
+ $result = $this->status(['id' => $id, $field => $value]);
|
|
|
+ $this->assertSame(HttpStatus::CUSTOM_ERROR, $result['code']);
|
|
|
+ $this->assertStringContainsString('关联活动', $result['msg']);
|
|
|
+ $this->assertStringContainsString('不能手动上下架', $result['msg']);
|
|
|
+ $this->assertSame($before, (array) DB::table('banner')->find($id));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ foreach ([['status' => null], ['manual_status' => null], ['status' => 'invalid'], ['manual_status' => []]] as $fields) {
|
|
|
+ $result = $this->status(['id' => $id] + $fields);
|
|
|
+ $this->assertSame(HttpStatus::VALIDATION_FAILED, $result['code']);
|
|
|
+ $this->assertSame($before, (array) DB::table('banner')->find($id));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public function test_bound_full_forms_ignore_status_inputs_and_new_bound_rows_keep_database_default_on(): void
|
|
|
+ {
|
|
|
+ $now = time();
|
|
|
+ $this->activity(7, $now - 120, $now + 600);
|
|
|
+ $id = $this->banner(['link' => '7', 'status' => 0]);
|
|
|
+ foreach ([['status' => 1], ['manual_status' => 0], ['status' => 0, 'manual_status' => 1],
|
|
|
+ ['status' => 0, 'manual_status' => null]] as $fields) {
|
|
|
+ $result = $this->save($fields + ['id' => $id, 'title' => '活动控制上下架']);
|
|
|
+ $this->assertSame(HttpStatus::OK, $result['code'], $result['msg']);
|
|
|
+ $this->assertSame(0, (int) DB::table('banner')->where('id', $id)->value('status'));
|
|
|
+ $this->assertState($this->decorated($now)[$id], null, 1, null, '7');
|
|
|
+ }
|
|
|
+ foreach ([['status' => 'invalid'], ['manual_status' => []], ['status' => null]] as $fields) {
|
|
|
+ $result = $this->save($fields + ['id' => $id, 'title' => '不应保存']);
|
|
|
+ $this->assertSame(HttpStatus::VALIDATION_FAILED, $result['code']);
|
|
|
+ $this->assertSame(0, (int) DB::table('banner')->where('id', $id)->value('status'));
|
|
|
+ $this->assertSame('活动控制上下架', DB::table('banner')->where('id', $id)->value('title'));
|
|
|
+ }
|
|
|
+ $result = $this->save(['link' => '7', 'status' => 0, 'manual_status' => 0]);
|
|
|
+ $this->assertSame(HttpStatus::OK, $result['code'], $result['msg']);
|
|
|
+ $newId = DB::table('banner')->latest('id')->value('id');
|
|
|
+ $this->assertSame(1, (int) DB::table('banner')->where('id', $newId)->value('status'));
|
|
|
+ $this->assertState($this->decorated($now)[$newId], null, 1, null, '7');
|
|
|
+ }
|
|
|
+
|
|
|
+ public function test_clearing_activity_link_restores_manual_control_and_rebinding_ignores_manual_off_input(): void
|
|
|
+ {
|
|
|
+ $now = time();
|
|
|
+ $this->activity(7, $now - 120, $now + 600);
|
|
|
+ $id = $this->banner(['link' => '7', 'status' => 0]);
|
|
|
+ $this->assertState($this->decorated($now)[$id], null, 1, null, '7');
|
|
|
+ $result = $this->save(['id' => $id, 'link' => null]);
|
|
|
+ $this->assertSame(HttpStatus::OK, $result['code'], $result['msg']);
|
|
|
+ $this->assertState($this->decorated($now)[$id], 0, 0, 'manual', null);
|
|
|
+ $result = $this->status(['id' => $id, 'manual_status' => 1]);
|
|
|
+ $this->assertSame(HttpStatus::OK, $result['code'], $result['msg']);
|
|
|
+ $this->assertState($this->decorated($now)[$id], 1, 1, null, null);
|
|
|
+ $result = $this->save(['id' => $id, 'link' => '7', 'status' => 0, 'manual_status' => 0]);
|
|
|
+ $this->assertSame(HttpStatus::OK, $result['code'], $result['msg']);
|
|
|
+ $this->assertSame(1, (int) DB::table('banner')->where('id', $id)->value('status'));
|
|
|
+ $this->assertState($this->decorated($now)[$id], null, 1, null, '7');
|
|
|
+ }
|
|
|
+
|
|
|
public function test_integer_and_leading_zero_activity_links_are_validated_and_canonicalized(): void
|
|
|
{
|
|
|
$this->activity(7, 1, 4102444799);
|
|
|
@@ -312,7 +455,7 @@ class BannerPublicationTest extends TestCase
|
|
|
$this->assertSame(3, DB::table('banner')->count());
|
|
|
}
|
|
|
|
|
|
- public function test_stale_missing_activity_link_can_be_edited_unchanged_and_taken_offline(): void
|
|
|
+ public function test_stale_missing_activity_link_can_be_edited_but_cannot_be_manually_toggled(): void
|
|
|
{
|
|
|
$id = $this->banner(['link' => '500', 'sort' => 31]);
|
|
|
$result = $this->save(['id' => $id, 'title' => '已删除活动的轮播图']);
|
|
|
@@ -321,10 +464,16 @@ class BannerPublicationTest extends TestCase
|
|
|
$this->assertSame(31, (int) DB::table('banner')->where('id', $id)->value('sort'));
|
|
|
$result = $this->save(['id' => $id, 'link' => '000500', 'status' => 0]);
|
|
|
$this->assertSame(HttpStatus::OK, $result['code'], $result['msg']);
|
|
|
- $this->assertState($this->decorated(time())[$id], 0, 0, 'manual', '500');
|
|
|
- $this->assertSame(HttpStatus::OK, $this->status(['id' => $id, 'status' => 1])['code']);
|
|
|
- $this->assertState($this->decorated(time())[$id], 1, 0, 'activity_missing', '500');
|
|
|
- $this->assertSame(HttpStatus::OK, $this->status(['id' => $id, 'status' => 0])['code']);
|
|
|
+ $this->assertState($this->decorated(time())[$id], null, 0, 'activity_missing', '500');
|
|
|
+ foreach (['status', 'manual_status'] as $field) {
|
|
|
+ foreach ([0, 1] as $value) {
|
|
|
+ $result = $this->status(['id' => $id, $field => $value]);
|
|
|
+ $this->assertSame(HttpStatus::CUSTOM_ERROR, $result['code']);
|
|
|
+ $this->assertStringContainsString('关联活动', $result['msg']);
|
|
|
+ $this->assertStringContainsString('不能手动上下架', $result['msg']);
|
|
|
+ $this->assertSame(1, (int) DB::table('banner')->where('id', $id)->value('status'));
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
public function test_admin_pagination_counts_hidden_rows_and_batches_activity_queries(): void
|
|
|
@@ -366,6 +515,144 @@ class BannerPublicationTest extends TestCase
|
|
|
$this->assertSame([1, 0, 1], array_column($page['data'], 'effective_status'));
|
|
|
}
|
|
|
|
|
|
+ public function test_platform_language_and_actual_status_filters_combine_without_mutating_data(): void
|
|
|
+ {
|
|
|
+ $now = time();
|
|
|
+ $this->activity(7, $now - 100, $now + 600);
|
|
|
+ $this->activity(8, $now - 100, $now - 60);
|
|
|
+ $this->activity(9, $now + 600, $now + 1200);
|
|
|
+ $this->activity(10, $now - 100, $now + 600, 0);
|
|
|
+ foreach ([
|
|
|
+ ['pc_zh_active', 'pc', 'zh', '7', 0, 110],
|
|
|
+ ['pc_zh_ended', 'pc', 'zh', '8', 1, 100],
|
|
|
+ ['pc_en_on', 'pc', 'en', null, 1, 90],
|
|
|
+ ['pc_en_off', 'pc', 'en', null, 0, 80],
|
|
|
+ ['app_zh_active', 'app', 'zh', '7', 0, 70],
|
|
|
+ ['app_zh_ended', 'app', 'zh', '8', 1, 60],
|
|
|
+ ['app_en_on', 'app', 'en', null, 1, 50],
|
|
|
+ ['app_en_off', 'app', 'en', null, 0, 40],
|
|
|
+ ['pc_zh_future', 'pc', 'zh', '9', 1, 30],
|
|
|
+ ['pc_zh_offline', 'pc', 'zh', '10', 1, 20],
|
|
|
+ ['pc_zh_missing', 'pc', 'zh', '999', 1, 10],
|
|
|
+ ] as [$name, $type, $lang, $link, $manual, $sort]) {
|
|
|
+ $ids[$name] = $this->banner(['type' => $type, 'lang' => $lang, 'link' => $link, 'status' => $manual, 'sort' => $sort]);
|
|
|
+ }
|
|
|
+ $bannerBefore = DB::table('banner')->orderBy('id')->get()->toJson();
|
|
|
+ $activityBefore = DB::table('activity_rewards')->orderBy('id')->get()->toJson();
|
|
|
+ $cases = [
|
|
|
+ [[], array_keys($ids)],
|
|
|
+ [['platform' => 'pc'], ['pc_zh_active', 'pc_zh_ended', 'pc_en_on', 'pc_en_off', 'pc_zh_future', 'pc_zh_offline', 'pc_zh_missing']],
|
|
|
+ [['platform' => 'h5'], ['app_zh_active', 'app_zh_ended', 'app_en_on', 'app_en_off']],
|
|
|
+ [['type' => 'app'], ['app_zh_active', 'app_zh_ended', 'app_en_on', 'app_en_off']],
|
|
|
+ [['platform' => 'h5', 'type' => 'app'], ['app_zh_active', 'app_zh_ended', 'app_en_on', 'app_en_off']],
|
|
|
+ [['platform' => 'pc', 'type' => 'pc', 'lang' => 'en'], ['pc_en_on', 'pc_en_off']],
|
|
|
+ [['lang' => 'zh'], ['pc_zh_active', 'pc_zh_ended', 'app_zh_active', 'app_zh_ended', 'pc_zh_future', 'pc_zh_offline', 'pc_zh_missing']],
|
|
|
+ [['lang' => 'en'], ['pc_en_on', 'pc_en_off', 'app_en_on', 'app_en_off']],
|
|
|
+ [['status' => 1], ['pc_zh_active', 'pc_en_on', 'app_zh_active', 'app_en_on']],
|
|
|
+ [['status' => '0'], ['pc_zh_ended', 'pc_en_off', 'app_zh_ended', 'app_en_off', 'pc_zh_future', 'pc_zh_offline', 'pc_zh_missing']],
|
|
|
+ [['platform' => 'pc', 'lang' => 'zh', 'status' => 0], ['pc_zh_ended', 'pc_zh_future', 'pc_zh_offline', 'pc_zh_missing']],
|
|
|
+ [['platform' => 'h5', 'lang' => 'en', 'status' => '1'], ['app_en_on']],
|
|
|
+ [['platform' => 'h5', 'lang' => 'zh', 'status' => '0'], ['app_zh_ended']],
|
|
|
+ [['platform' => 'h5', 'status' => 0], ['app_zh_ended', 'app_en_off']],
|
|
|
+ [['platform' => 'pc', 'lang' => 'fr', 'status' => 1], []],
|
|
|
+ ];
|
|
|
+ foreach ($cases as [$filters, $names]) {
|
|
|
+ $page = $this->listing($filters);
|
|
|
+ $expectedIds = array_map(fn ($name) => $ids[$name], $names);
|
|
|
+ $this->assertSame(count($expectedIds), $page['total']);
|
|
|
+ $this->assertSame($expectedIds, array_column($page['data'], 'id'));
|
|
|
+ if (isset($filters['platform'])) {
|
|
|
+ foreach ($page['data'] as $row) $this->assertSame($filters['platform'] === 'h5' ? 'app' : 'pc', $row['type']);
|
|
|
+ }
|
|
|
+ if (array_key_exists('status', $filters)) {
|
|
|
+ foreach ($page['data'] as $row) $this->assertSame((int) $filters['status'], $row['status']);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $this->assertSame($bannerBefore, DB::table('banner')->orderBy('id')->get()->toJson());
|
|
|
+ $this->assertSame($activityBefore, DB::table('activity_rewards')->orderBy('id')->get()->toJson());
|
|
|
+ }
|
|
|
+
|
|
|
+ public function test_actual_status_is_filtered_before_pagination_and_activity_lookup_stays_batched(): void
|
|
|
+ {
|
|
|
+ $now = time();
|
|
|
+ $this->activity(7, $now - 100, $now + 600);
|
|
|
+ $this->activity(8, $now - 100, $now - 60);
|
|
|
+ $up = $down = [];
|
|
|
+ for ($i = 0; $i < 12; $i++) {
|
|
|
+ $id = $this->banner(['type' => 'app', 'lang' => 'zh', 'link' => $i % 2 ? '8' : '7', 'status' => $i % 2, 'sort' => $i]);
|
|
|
+ if ($i % 2) $down[] = $id; else $up[] = $id;
|
|
|
+ }
|
|
|
+ $this->banner(['type' => 'app', 'lang' => 'en', 'link' => '7', 'sort' => 100]);
|
|
|
+ $this->banner(['type' => 'pc', 'lang' => 'zh', 'link' => '7', 'sort' => 101]);
|
|
|
+ $up = array_reverse($up); $down = array_reverse($down);
|
|
|
+ $snapshot = DB::table('banner')->orderBy('id')->get()->toJson();
|
|
|
+ $filters = ['platform' => 'h5', 'lang' => 'zh', 'status' => 1, 'limit' => 2];
|
|
|
+ DB::connection()->enableQueryLog();
|
|
|
+ $first = $this->listing($filters);
|
|
|
+ $queries = DB::connection()->getQueryLog();
|
|
|
+ DB::connection()->disableQueryLog();
|
|
|
+ $this->assertCount(2, $queries, 'Status filtering should fetch matching candidates and all referenced activities in two queries.');
|
|
|
+ $this->assertSame(6, $first['total']);
|
|
|
+ $this->assertSame(array_slice($up, 0, 2), array_column($first['data'], 'id'));
|
|
|
+ $seen = array_column($first['data'], 'id');
|
|
|
+ foreach ([2, 3, 4] as $number) {
|
|
|
+ $page = $this->listing($filters + ['page' => $number]);
|
|
|
+ $this->assertSame(6, $page['total']);
|
|
|
+ $this->assertSame(array_slice($up, ($number - 1) * 2, 2), array_column($page['data'], 'id'));
|
|
|
+ $seen = array_merge($seen, array_column($page['data'], 'id'));
|
|
|
+ }
|
|
|
+ $this->assertSame($up, $seen);
|
|
|
+ foreach ([1, 2] as $number) {
|
|
|
+ $page = $this->listing(['platform' => 'h5', 'type' => 'app', 'lang' => 'zh', 'status' => '0', 'page' => $number, 'limit' => 4]);
|
|
|
+ $this->assertSame(6, $page['total']);
|
|
|
+ $this->assertSame(array_slice($down, ($number - 1) * 4, 4), array_column($page['data'], 'id'));
|
|
|
+ }
|
|
|
+ $this->assertSame($snapshot, DB::table('banner')->orderBy('id')->get()->toJson());
|
|
|
+ }
|
|
|
+
|
|
|
+ public function test_empty_optional_filters_keep_all_languages_and_default_pagination(): void
|
|
|
+ {
|
|
|
+ for ($i = 0; $i < 16; $i++) {
|
|
|
+ $ids[] = $this->banner(['type' => $i % 2 ? 'pc' : 'app', 'lang' => $i % 2 ? 'zh' : 'en', 'status' => $i % 2]);
|
|
|
+ }
|
|
|
+ $ids = array_reverse($ids);
|
|
|
+ $snapshot = DB::table('banner')->orderBy('id')->get()->toJson();
|
|
|
+ foreach ([[], ['platform' => null, 'type' => null, 'lang' => null, 'status' => null],
|
|
|
+ ['platform' => '', 'type' => '', 'lang' => '', 'status' => '']] as $filters) {
|
|
|
+ $page = $this->listing($filters);
|
|
|
+ $this->assertSame(16, $page['total']);
|
|
|
+ $this->assertCount(15, $page['data']);
|
|
|
+ $this->assertSame(array_slice($ids, 0, 15), array_column($page['data'], 'id'));
|
|
|
+ $this->assertSame(['zh', 'en'], array_values(array_unique(array_column($page['data'], 'lang'))));
|
|
|
+ $last = $this->listing($filters + ['page' => 2]);
|
|
|
+ $this->assertSame(16, $last['total']);
|
|
|
+ $this->assertSame(array_slice($ids, 15), array_column($last['data'], 'id'));
|
|
|
+ }
|
|
|
+ $largePage = $this->listing(['limit' => 200]);
|
|
|
+ $this->assertSame(16, $largePage['total']);
|
|
|
+ $this->assertSame($ids, array_column($largePage['data'], 'id'));
|
|
|
+ $this->assertSame($snapshot, DB::table('banner')->orderBy('id')->get()->toJson());
|
|
|
+ }
|
|
|
+
|
|
|
+ public function test_invalid_filters_and_conflicting_platform_aliases_are_rejected_without_mutation(): void
|
|
|
+ {
|
|
|
+ $this->banner(['type' => 'app', 'lang' => 'en']);
|
|
|
+ $snapshot = DB::table('banner')->get()->toJson();
|
|
|
+ foreach ([
|
|
|
+ ['platform' => 'app'], ['platform' => 'android'], ['platform' => ['pc']],
|
|
|
+ ['type' => 'h5'], ['type' => ['app']],
|
|
|
+ ['platform' => 'pc', 'type' => 'app'], ['platform' => 'h5', 'type' => 'pc'],
|
|
|
+ ['lang' => ['zh']], ['lang' => 123], ['lang' => 'abcdefghijk'],
|
|
|
+ ['status' => -1], ['status' => 2], ['status' => 'up'], ['status' => '1.5'], ['status' => ['0']],
|
|
|
+ ['page' => 0], ['limit' => 0], ['limit' => 201],
|
|
|
+ ] as $filters) {
|
|
|
+ $this->request('index', $filters);
|
|
|
+ $result = (new AdminBanner())->index()->getData(true);
|
|
|
+ $this->assertSame(HttpStatus::VALIDATION_FAILED, $result['code']);
|
|
|
+ }
|
|
|
+ $this->assertSame($snapshot, DB::table('banner')->get()->toJson());
|
|
|
+ }
|
|
|
+
|
|
|
public function test_status_route_requires_existing_banner_edit_permission_and_preserves_superadmin_access(): void
|
|
|
{
|
|
|
Schema::create('menus', function (Blueprint $t) {
|
|
|
@@ -398,7 +685,7 @@ class BannerPublicationTest extends TestCase
|
|
|
$this->assertSame(AdminBanner::class . '@status', $route->getAction('uses'));
|
|
|
$id = $this->banner();
|
|
|
$changeStatus = function (int $userId, int $status) use ($router, $id): array {
|
|
|
- $this->request('status', ['id' => $id, 'status' => $status, 'user' => (object) ['id' => $userId]]);
|
|
|
+ $this->request('status', ['id' => $id, 'manual_status' => $status, 'user' => (object) ['id' => $userId]]);
|
|
|
$request = $this->app['request'];
|
|
|
$route = $router->getRoutes()->match($request);
|
|
|
$request->setRouteResolver(fn () => $route);
|