118 lines
3.0 KiB
PHP
118 lines
3.0 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Ssh;
|
|
|
|
/**
|
|
* Test double: script command output by substring, record everything.
|
|
*/
|
|
class FakeRemoteShell implements RemoteShell
|
|
{
|
|
/** @var array<string, CommandResult|callable> */
|
|
private array $scripts = [];
|
|
|
|
/** @var array<int, string> */
|
|
private array $recorded = [];
|
|
|
|
/** @var array<string, string> */
|
|
private array $files = [];
|
|
|
|
/** @var array<int, array{0:string,1:string,2:string,3?:string}> 3 is the private key, only for 'key' connections. */
|
|
public array $connections = [];
|
|
|
|
public string $fingerprint = 'SHA256:fakehostkeyfingerprint';
|
|
|
|
/** When set, connect attempts throw (simulates an unreachable/rebooting host). */
|
|
public bool $failConnect = false;
|
|
|
|
private CommandResult $default;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->default = CommandResult::success();
|
|
}
|
|
|
|
public function script(string $substring, CommandResult|callable $result): static
|
|
{
|
|
$this->scripts[$substring] = $result;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function defaultResult(CommandResult $result): static
|
|
{
|
|
$this->default = $result;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function connectWithPassword(string $host, string $user, string $password): void
|
|
{
|
|
if ($this->failConnect) {
|
|
throw new \RuntimeException("connection refused: {$host}");
|
|
}
|
|
$this->connections[] = ['password', $host, $user];
|
|
}
|
|
|
|
public function connectWithKey(string $host, string $user, string $privateKey, ?string $expectedFingerprint = null): void
|
|
{
|
|
if ($this->failConnect) {
|
|
throw new \RuntimeException("connection refused: {$host}");
|
|
}
|
|
$this->connections[] = ['key', $host, $user, $privateKey];
|
|
}
|
|
|
|
public function run(string $command): CommandResult
|
|
{
|
|
$this->recorded[] = $command;
|
|
|
|
foreach ($this->scripts as $substring => $result) {
|
|
if (str_contains($command, $substring)) {
|
|
return is_callable($result) ? $result($command) : $result;
|
|
}
|
|
}
|
|
|
|
return $this->default;
|
|
}
|
|
|
|
public function putFile(string $remotePath, string $contents): void
|
|
{
|
|
$this->files[$remotePath] = $contents;
|
|
}
|
|
|
|
public function hostKeyFingerprint(): string
|
|
{
|
|
return $this->fingerprint;
|
|
}
|
|
|
|
// --- introspection helpers for tests ---
|
|
|
|
/** @return array<int, string> */
|
|
public function recorded(): array
|
|
{
|
|
return $this->recorded;
|
|
}
|
|
|
|
public function ran(string $substring): bool
|
|
{
|
|
foreach ($this->recorded as $command) {
|
|
if (str_contains($command, $substring)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/** @return array<string, string> */
|
|
public function files(): array
|
|
{
|
|
return $this->files;
|
|
}
|
|
|
|
/** @return array<int, mixed> */
|
|
public function connectionsWith(string $type): array
|
|
{
|
|
return array_values(array_filter($this->connections, fn ($c) => $c[0] === $type));
|
|
}
|
|
}
|