Files
zyt/server/tests/fixtures/FakeMobileRecordingCos.php
2026-11-09 14:51:31 +08:00

86 lines
4.3 KiB
PHP

<?php
declare(strict_types=1);
use app\common\service\mobilerecording\MobileRecordingCos;
/** In-memory synthetic COS. No configuration, credentials, SDK or network access. */
final class FakeMobileRecordingCos extends MobileRecordingCos
{
public array $current = ['bucket' => 'synthetic-1250000000', 'region' => 'ap-guangzhou'];
public array $objects = [];
public array $multipart = [];
public array $calls = [];
public int $losePartAck = 0;
public int $loseFinishAck = 0;
public int $failHead = 0;
public int $denyHead = 0;
public int $failDelete = 0;
public int $loseCreateAck = 0;
public int $partWrites = 0;
public int $finishes = 0;
public array $deletedVersions = [];
public $onPart = null;
public function location(): array { return $this->current; }
public function key(array $o): string { return $o['bucket'] . '|' . $o['region'] . '|' . $o['object_key']; }
public function initiate(array $o): string
{
$id = 'fake-' . (count($this->multipart) + 1);
$this->multipart[$id] = ['object' => $o, 'parts' => []];
$this->calls[] = 'initiate';
if ($this->loseCreateAck-- > 0) { throw new RuntimeException('synthetic create acknowledgement lost'); }
return $id;
}
public function part(array $o, int $number, string $cipher): string
{
if (!isset($this->multipart[$o['multipart_id']])) { throw new RuntimeException('synthetic session missing'); }
$this->multipart[$o['multipart_id']]['parts'][$number] = $cipher;
$this->partWrites++; $this->calls[] = 'part';
if ($this->onPart !== null) { $callback = $this->onPart; $this->onPart = null; $callback(); }
if ($this->losePartAck-- > 0) { throw new RuntimeException('synthetic part acknowledgement lost'); }
return md5($cipher);
}
public function finish(array $o, array $parts): void
{
$session = $this->multipart[$o['multipart_id']] ?? null;
if (!$session) { throw new RuntimeException('synthetic multipart absent'); }
$bytes = '';
foreach ($parts as $part) {
$body = $session['parts'][$part['PartNumber']] ?? '';
if (md5($body) !== $part['ETag']) { throw new RuntimeException('synthetic part mismatch'); }
$bytes .= $body;
}
$this->objects[$this->key($o)] = ['bytes' => $bytes, 'context' => $o['context_hash'], 'version_id' => 'version-' . (++$this->finishes)];
unset($this->multipart[$o['multipart_id']]);
$this->calls[] = 'finish';
if ($this->loseFinishAck-- > 0) { throw new RuntimeException('synthetic finish acknowledgement lost'); }
}
public function head(array $o, bool $pinned = false): ?array
{
$this->calls[] = 'head';
if ($this->denyHead-- > 0) { throw new DomainException('云端存储授权不可用,请联系管理员检查配置'); }
if ($this->failHead-- > 0) { throw new RuntimeException('synthetic COS down'); }
$object = $this->objects[$this->key($o)] ?? null;
if (!$object || ($pinned && $o['version_id'] !== '' && $o['version_id'] !== $object['version_id'])) { return null; }
return ['size' => strlen($object['bytes']), 'context' => $object['context'], 'version_id' => $object['version_id']];
}
public function range(array $o, int $start, int $end): string
{
$object = $this->objects[$this->key($o)] ?? null;
if (!$object || $o['version_id'] !== $object['version_id']) { throw new RuntimeException('synthetic pinned object absent'); }
if ($end - $start + 1 > 1048592) { throw new RuntimeException('unbounded cloud read'); }
$this->calls[] = 'range:' . $start . '-' . $end;
return substr($object['bytes'], $start, $end - $start + 1);
}
public function delete(array $o): void
{
if ($this->failDelete-- > 0) { throw new RuntimeException('synthetic delete failed'); }
$object = $this->objects[$this->key($o)] ?? null;
if ($object && $object['version_id'] !== $o['version_id']) { throw new RuntimeException('unversioned delete forbidden in fixture'); }
$this->deletedVersions[] = $o['version_id'];
unset($this->objects[$this->key($o)]);
$this->calls[] = 'delete';
}
public function abort(array $o): void { unset($this->multipart[$o['multipart_id']]); $this->calls[] = 'abort'; }
}