|
@@ -96,6 +96,7 @@ class AnnouncementTest extends TestCase
|
|
|
public function test_options_defaults_create_sender_and_no_approval_state(): void
|
|
public function test_options_defaults_create_sender_and_no_approval_state(): void
|
|
|
{
|
|
{
|
|
|
$options = $this->service->options();
|
|
$options = $this->service->options();
|
|
|
|
|
+ $this->assertSame(['none', 'external', 'internal'], array_column($options['click_types'], 'value'));
|
|
|
$this->assertSame(['content', 'image', 'update'], array_column($options['categories'], 'value'));
|
|
$this->assertSame(['content', 'image', 'update'], array_column($options['categories'], 'value'));
|
|
|
$this->assertSame(['PC', 'H5', 'APP'], array_column($options['platforms'], 'value'));
|
|
$this->assertSame(['PC', 'H5', 'APP'], array_column($options['platforms'], 'value'));
|
|
|
$row = $this->create();
|
|
$row = $this->create();
|
|
@@ -194,14 +195,79 @@ class AnnouncementTest extends TestCase
|
|
|
]);
|
|
]);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- public function test_click_targets_use_explicit_types_and_stale_targets_are_cleared(): void
|
|
|
|
|
|
|
+ public function test_announcement_activity_linkage_is_removed_including_historical_links(): void
|
|
|
{
|
|
{
|
|
|
- $row = $this->create(['click_type' => 'activity', 'activity_id' => 1]);
|
|
|
|
|
- $this->assertSame(1, $row['activity_id']); $this->assertNull($row['link']);
|
|
|
|
|
- $row = $this->service->save(['id' => $row['id'], 'click_type' => 'internal', 'link' => '/activity/1']);
|
|
|
|
|
- $this->assertNull($row['activity_id']);
|
|
|
|
|
|
|
+ $row = $this->create(['click_type' => 'external', 'link' => 'https://example.com']);
|
|
|
|
|
+ DB::table('announcements')->where('id', $row['id'])->update(['click_type' => 'activity', 'activity_id' => 1]);
|
|
|
|
|
+ $legacy = $this->service->detail($row['id']);
|
|
|
|
|
+ $this->assertSame('none', $legacy['click_type']); $this->assertNull($legacy['link']);
|
|
|
|
|
+ $this->assertArrayNotHasKey('activity_id', $legacy);
|
|
|
|
|
+ $row = $this->service->save(['id' => $row['id'], 'click_type' => 'internal', 'link' => '/news']);
|
|
|
|
|
+ $this->assertArrayNotHasKey('activity_id', $row);
|
|
|
$row = $this->service->save(['id' => $row['id'], 'click_type' => 'none']);
|
|
$row = $this->service->save(['id' => $row['id'], 'click_type' => 'none']);
|
|
|
- $this->assertNull($row['activity_id']); $this->assertNull($row['link']);
|
|
|
|
|
|
|
+ $this->assertArrayNotHasKey('activity_id', $row); $this->assertNull($row['link']);
|
|
|
|
|
+ Schema::drop('activity_rewards');
|
|
|
|
|
+ $this->assertArrayNotHasKey('activity_id', $this->create());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function test_normal_announcements_do_not_require_or_validate_a_hidden_activity_picker(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ foreach (['none' => null, 'external' => 'https://example.com', 'internal' => '/news'] as $click => $link) {
|
|
|
|
|
+ foreach ([null, 0, '', ['stale picker value']] as $activity) {
|
|
|
|
|
+ $row = $this->create(['click_type' => $click, 'link' => $link, 'activity_id' => $activity]);
|
|
|
|
|
+ $this->assertArrayNotHasKey('activity_id', $row); $this->assertSame($click, $row['click_type']);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ foreach ([null, 0, [], 'invalid'] as $activity) {
|
|
|
|
|
+ try { $this->create(['click_type' => 'activity', 'activity_id' => $activity]); $this->fail('Invalid activity accepted'); }
|
|
|
|
|
+ catch (ValidationException $e) { $this->assertTrue($e->validator->errors()->has('click_type')); }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function test_missing_websocket_table_save_failure_is_atomic_and_migration_restores_saving(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $this->app->instance('log', new \Psr\Log\NullLogger());
|
|
|
|
|
+ Schema::drop('site_realtime_events');
|
|
|
|
|
+ $this->request(['click_type' => 'none', 'translations' => ['zh' => ['title' => '普通公告', 'content' => '公告正文']]]);
|
|
|
|
|
+ $controller = new AnnouncementController();
|
|
|
|
|
+ $failed = $controller->submit($this->service)->getData(true);
|
|
|
|
|
+ $this->assertSame(-3, $failed['code']);
|
|
|
|
|
+ $this->assertSame('实时通知服务未初始化,请先完成系统更新', $failed['msg']);
|
|
|
|
|
+ $this->assertSame(0, DB::table('announcements')->count());
|
|
|
|
|
+ $this->assertSame(0, DB::table('operation_audits')->count());
|
|
|
|
|
+ (require dirname(__DIR__, 2) . '/database/migrations/2026_10_27_150000_create_site_realtime_events.php')->up();
|
|
|
|
|
+ $saved = $controller->submit($this->service)->getData(true);
|
|
|
|
|
+ $this->assertSame(0, $saved['code']); $this->assertArrayNotHasKey('activity_id', $saved['data']);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** @dataProvider saveErrors */
|
|
|
|
|
+ public function test_save_failures_report_the_reason_without_exposing_sql(string $state, int $code, string $expected): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $this->app->instance('log', new \Psr\Log\NullLogger());
|
|
|
|
|
+ $error = new \PDOException('SQLSTATE[' . $state . '] secret-value SELECT * FROM bot_announcements');
|
|
|
|
|
+ $error->errorInfo = [$state, $code, 'secret-value'];
|
|
|
|
|
+ $service = new class($error) extends AnnouncementService {
|
|
|
|
|
+ private \Throwable $error;
|
|
|
|
|
+ public function __construct(\Throwable $error) { $this->error = $error; }
|
|
|
|
|
+ public function save(array $input): array { throw $this->error; }
|
|
|
|
|
+ };
|
|
|
|
|
+ $body = (new AnnouncementController())->submit($service)->getData(true);
|
|
|
|
|
+ $this->assertSame(-3, $body['code']); $this->assertSame($expected, $body['msg']);
|
|
|
|
|
+ $this->assertStringNotContainsString('secret-value', json_encode($body));
|
|
|
|
|
+ $this->assertStringNotContainsString('SELECT', json_encode($body));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static function saveErrors(): array
|
|
|
|
|
+ {
|
|
|
|
|
+ return [
|
|
|
|
|
+ ['42S02', 1146, '公告数据未初始化,请先完成系统更新'],
|
|
|
|
|
+ ['42S22', 1054, '公告字段结构未更新完整,请先完成系统更新'],
|
|
|
|
|
+ ['HY000', 2002, '数据库连接失败,请检查数据库服务后重新保存'],
|
|
|
|
|
+ ['22001', 1406, '公告内容超出存储长度,请缩短内容后保存'],
|
|
|
|
|
+ ['40001', 1213, '公告数据正在被更新,请重新提交'],
|
|
|
|
|
+ ['23000', 1062, '公告数据重复,请刷新页面后重新保存'],
|
|
|
|
|
+ ['42000', 1064, '公告数据库操作失败(错误代码1064)'],
|
|
|
|
|
+ ];
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public function test_audit_failure_rolls_back_create_edit_and_status(): void
|
|
public function test_audit_failure_rolls_back_create_edit_and_status(): void
|