1, 'inserted' => 2, 'errors' => []]; public static function syncImChatArchiveBatch($days, $limit, $id): array { return self::$result; } } final class ImCommandConsoleFixture { public static bool $fail = true; public static function call($name, $params = []): void { if (self::$fail) throw new RuntimeException('云端暂不可用'); } } final class ImCommandTaskFixture { public static array $writes = []; public static function where($key, $value): self { return new self(); } public function update(array $data): void { self::$writes[] = $data; } } final class ImCommandLogFixture { public static function error($message): void {} } class_alias(ImCommandLogicFixture::class, 'app\adminapi\logic\tcm\DiagnosisLogic'); class_alias(ImCommandConsoleFixture::class, 'think\facade\Console'); class_alias(ImCommandTaskFixture::class, 'app\common\model\Crontab'); class_alias(ImCommandLogFixture::class, 'think\facade\Log'); require dirname(__DIR__) . '/vendor/autoload.php'; function imCommandExpect(bool $condition, string $message): void { if (!$condition) throw new RuntimeException($message); } $task = ['id' => 3, 'params' => '--since-days=0 --limit=200', 'command' => 'sync_im_chat_archive', 'max_time' => 0]; \app\common\command\Crontab::start($task); imCommandExpect(ImCommandTaskFixture::$writes[0]['error'] === '云端暂不可用', 'sync failure remains visible in scheduler'); imCommandExpect(ImCommandTaskFixture::$writes[0]['status'] === \app\common\enum\CrontabEnum::START, 'IM sync retries next scheduled round'); ImCommandTaskFixture::$writes = []; \app\common\command\Crontab::start(array_merge($task, ['command' => 'unrelated_command'])); imCommandExpect(ImCommandTaskFixture::$writes[0]['status'] === \app\common\enum\CrontabEnum::ERROR, 'other command failure behavior is unchanged'); ImCommandTaskFixture::$writes = []; ImCommandConsoleFixture::$fail = false; \app\common\command\Crontab::start($task); imCommandExpect(ImCommandTaskFixture::$writes[0]['error'] === '', 'successful round clears previous error'); $command = new \app\common\command\SyncImChatArchive(); $input = new \think\console\Input([]); $output = new \think\console\Output('buffer'); ImCommandLogicFixture::$result['errors'] = ['doctor fixture unavailable']; try { $command->run($input, $output); throw new RuntimeException('command silently succeeded'); } catch (RuntimeException $e) { imCommandExpect(str_contains($e->getMessage(), 'IM 聊天归档未完全同步'), 'command propagates failure even though Console::call discards return codes'); } ImCommandLogicFixture::$result['errors'] = []; imCommandExpect($command->run(new \think\console\Input([]), new \think\console\Output('buffer')) === 0, 'successful command returns zero'); echo "IM archive command and scheduler propagation: OK (no database/network)\n";