1001, 'msg_id' => 2001, 'userid' => 3001, ], $attr)); $todo->save(); return $todo; } public function test_remind_columns_persist() { $todo = $this->makeTodo(['remind_at' => '2026-06-02 09:00:00']); $fresh = WebSocketDialogMsgTodo::whereId($todo->id)->first(); $this->assertEquals('2026-06-02 09:00:00', $fresh->remind_at->format('Y-m-d H:i:s')); $this->assertNull($fresh->reminded_at); } public function test_due_reminders_selects_only_due_unreminded_undone() { $past = Carbon::now()->subMinutes(5)->format('Y-m-d H:i:s'); $future = Carbon::now()->addHours(2)->format('Y-m-d H:i:s'); $due = $this->makeTodo(['userid' => 1, 'remind_at' => $past]); $future1 = $this->makeTodo(['userid' => 2, 'remind_at' => $future]); $already = $this->makeTodo(['userid' => 3, 'remind_at' => $past, 'reminded_at' => Carbon::now()]); $done = $this->makeTodo(['userid' => 4, 'remind_at' => $past, 'done_at' => Carbon::now()]); $noRemind = $this->makeTodo(['userid' => 5]); $ids = WebSocketDialogMsgTodo::dueReminders()->pluck('id')->toArray(); $this->assertContains($due->id, $ids); $this->assertNotContains($future1->id, $ids); $this->assertNotContains($already->id, $ids); $this->assertNotContains($done->id, $ids); $this->assertNotContains($noRemind->id, $ids); } public function test_set_todo_remind_sets_and_resets_and_clears() { // 同一消息两人,预置已提醒状态以验证会被重置 $a = $this->makeTodo(['msg_id' => 5001, 'userid' => 11, 'reminded_at' => Carbon::now()]); $b = $this->makeTodo(['msg_id' => 5001, 'userid' => 12, 'reminded_at' => Carbon::now()]); $msg = new \App\Models\WebSocketDialogMsg(); $msg->id = 5001; // 设提醒:写入 remind_at,并把 reminded_at 重置为 null $affected = $msg->setTodoRemind([11, 12], '2026-06-05 10:00:00'); $this->assertSame(2, $affected); foreach ([$a, $b] as $row) { $fresh = WebSocketDialogMsgTodo::whereId($row->id)->first(); $this->assertStringStartsWith('2026-06-05 10:00:00', $fresh->remind_at->format('Y-m-d H:i:s')); $this->assertNull($fresh->reminded_at, '改时间后应允许再次提醒'); } // 取消提醒:remind_at 置 null $msg->setTodoRemind([11], null); $this->assertNull(WebSocketDialogMsgTodo::whereId($a->id)->first()->remind_at); // 未传 userid 时不动任何行 $this->assertSame(0, $msg->setTodoRemind([], '2026-06-05 10:00:00')); } private function makeUser(string $email): User { $user = User::createInstance([ 'email' => $email, 'userimg' => '', 'nickname' => 'TestUser_' . substr(md5($email), 0, 6), 'profession' => '', 'password' => md5('123456'), ]); $user->save(); return $user; } /** * 镜像 msg__todoremind 的权限闸门: * 开关 close 且改到「自己以外的人」时,需操作者命中 checkTodoOwnerPermission。 * * 注意:此逻辑为 DialogController::msg__todoremind() 权限逻辑的镜像, * 若 msg__todoremind 权限逻辑改动需同步更新此方法。 */ private function remindGateAllow(WebSocketDialog $dialog, string $switch, int $sender, array $userids): bool { if ($switch !== 'close') { return true; } $others = array_diff(array_map('intval', $userids), [$sender]); if (!$others) { return true; // 只改自己 } return $dialog->checkTodoOwnerPermission($sender); } public function test_remind_edit_permission_follows_todo_gate() { $owner = $this->makeUser('r_o@test.local'); $member = $this->makeUser('r_m@test.local'); $dialog = WebSocketDialog::createGroup('Test_remind', [$owner->userid, $member->userid], 'user', $owner->userid)->fresh(); $this->assertTrue($this->remindGateAllow($dialog, 'close', $member->userid, [$member->userid])); // 改自己→放行 $this->assertFalse($this->remindGateAllow($dialog, 'close', $member->userid, [$owner->userid])); // 改他人→拒绝 $this->assertTrue($this->remindGateAllow($dialog, 'close', $owner->userid, [$member->userid])); // 群主改他人→放行 $this->assertTrue($this->remindGateAllow($dialog, 'open', $member->userid, [$owner->userid])); // open→放行 } public function test_build_remind_text_produces_mention_spans() { $a = $this->makeUser('rt_a@test.local'); $b = $this->makeUser('rt_b@test.local'); $text = TodoRemindTask::buildRemindText([$a->userid, $b->userid]); $this->assertStringContainsString("userid}\">@{$a->nickname}", $text); $this->assertStringContainsString("userid}\">@{$b->nickname}", $text); $this->assertStringContainsString('你有一条待办到提醒时间啦', $text); } public function test_msg_join_group_extracts_text_mention_from_spans() { $owner = $this->makeUser('tx_o@test.local'); $a = $this->makeUser('tx_a@test.local'); $b = $this->makeUser('tx_b@test.local'); $dialog = WebSocketDialog::createGroup('Test_text_mention', [$owner->userid, $a->userid, $b->userid], 'user', $owner->userid)->fresh(); $msg = new \App\Models\WebSocketDialogMsg(); $msg->dialog_id = $dialog->id; $msg->userid = $owner->userid; $msg->type = 'text'; $msg->msg = ['text' => TodoRemindTask::buildRemindText([$a->userid, $b->userid])]; $result = $msg->msgJoinGroup($dialog); $mentions = array_map('intval', $result['mentions']); sort($mentions); $expected = [$a->userid, $b->userid]; sort($expected); $this->assertEquals($expected, $mentions); } }