tmpDir = sys_get_temp_dir() . '/magesail_ts_' . uniqid('', true); mkdir($this->tmpDir, 0777, true); } protected function tearDown(): void { foreach (glob($this->tmpDir . '/*') ?: [] as $f) { if (is_file($f)) { @unlink($f); } } @rmdir($this->tmpDir); parent::tearDown(); } public function testGetStatusReturnsNotRunningWhenPidFileMissing(): void { $shell = $this->createMock(Shell::class); $shell->expects(self::never())->method('execute'); $scope = $this->createMock(ScopeConfigInterface::class); $provisioner = $this->createMock(TunnelStoreProvisioner::class); $registry = $this->createMock(TunnelRegistry::class); $paths = $this->createMock(TunnelProcessPaths::class); $paths->method('getPidFilePath')->willReturn($this->tmpDir . '/n.pid'); $paths->method('getKeyFilePath')->willReturn($this->tmpDir . '/n.key'); $status = new TunnelStatus($shell, $scope, $provisioner, $registry, $paths); $st = $status->getStatus('any'); self::assertFalse($st['running']); self::assertNull($st['pid']); self::assertNull($st['key']); } public function testGetStatusRunningWhenProcessAlive(): void { $pidFile = $this->tmpDir . '/t.pid'; $keyFile = $this->tmpDir . '/t.key'; file_put_contents($pidFile, (string) getmypid()); file_put_contents($keyFile, 'hs://key'); $shell = $this->createMock(Shell::class); $shell->method('execute')->willReturn(''); $scope = $this->createMock(ScopeConfigInterface::class); $provisioner = $this->createMock(TunnelStoreProvisioner::class); $registry = $this->createMock(TunnelRegistry::class); $paths = $this->createMock(TunnelProcessPaths::class); $paths->method('getPidFilePath')->willReturn($pidFile); $paths->method('getKeyFilePath')->willReturn($keyFile); $status = new TunnelStatus($shell, $scope, $provisioner, $registry, $paths); $st = $status->getStatus('t1'); self::assertTrue($st['running']); self::assertSame(getmypid(), $st['pid']); self::assertSame('hs://key', $st['key']); } public function testGetStatusClearsStalePidWhenProcessDeadAndAutoRestartOff(): void { $pidFile = $this->tmpDir . '/dead.pid'; file_put_contents($pidFile, '999999'); $shell = $this->createMock(Shell::class); $shell->method('execute')->willThrowException(new \RuntimeException('not running')); $scope = $this->createMock(ScopeConfigInterface::class); $scope->method('getValue')->willReturnCallback(static function ($path) { return $path === 'magesail/settings/auto_restart' ? false : null; }); $provisioner = $this->createMock(TunnelStoreProvisioner::class); $provisioner->expects(self::once())->method('teardownAfterStaleProcess')->with('tid'); $registry = $this->createMock(TunnelRegistry::class); $paths = $this->createMock(TunnelProcessPaths::class); $paths->method('getPidFilePath')->willReturn($pidFile); $paths->method('getKeyFilePath')->willReturn($this->tmpDir . '/dead.key'); $status = new TunnelStatus($shell, $scope, $provisioner, $registry, $paths); $st = $status->getStatus('tid'); self::assertFalse($st['running']); self::assertFileDoesNotExist($pidFile); } public function testIsProcessAliveFalseForNonPositivePid(): void { $shell = $this->createMock(Shell::class); $shell->expects(self::never())->method('execute'); $scope = $this->createMock(ScopeConfigInterface::class); $provisioner = $this->createMock(TunnelStoreProvisioner::class); $registry = $this->createMock(TunnelRegistry::class); $paths = $this->createMock(TunnelProcessPaths::class); $paths->method('getPidFilePath')->willReturn($this->tmpDir . '/x.pid'); $paths->method('getKeyFilePath')->willReturn($this->tmpDir . '/x.key'); $status = new TunnelStatus($shell, $scope, $provisioner, $registry, $paths); self::assertFalse($status->isProcessAlive(0)); self::assertFalse($status->isProcessAlive(-1)); } }