Files
janniclas.loosenandClaude Opus 5.5 72eeaa46ad test: FakeChangeLog and SchemaFixture instead of repeated mocks (T2)
- tests/Support/FakeChangeLog extends ChangeLog: records record()/recordPartial()
  calls, increasing ids, optional failure; new FakeChangeLog(id) / partialsOnly() /
  any(id) / forbidden() mirror what the replaced mocks allowed. assertRecorded() /
  assertPartials() check count and the named arguments. Calls a fake does not allow
  throw and are also verified in TestCase::tearDown (one assertion per test that
  uses a fake), so swallowed change-log calls still fail like a never() expectation.
- All ChangeLog mocks of ability/integration tests replaced (the admin tests that
  stub query/purge/clear keep their mocks); expectException tests that checked the
  partial log now catch and assert.
- SchemaFixture::managed(?divi) replaces the 21 isManaged-only ContentSchema mocks;
  DiviFixtures::schema() uses it.
Tests 1397 -> 1404 (FakeChangeLogTest); no test has fewer assertions.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 17:53:54 +02:00

182 lines
5.8 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace LIT\MCP\Tests\Support;
use LIT\MCP\ChangeLog\ChangeLog;
use LogicException;
use PHPUnit\Framework\Assert;
use Throwable;
/**
* ChangeLog without a database. Allowed calls are kept in $records / $partials (arguments by
* name); record() returns increasing ids from $firstId, or throws $failure after noting the call.
*
* What a fake allows mirrors the Mockery mocks it replaces:
* - new FakeChangeLog(id) record() only (allows('record')->andReturn(id))
* - FakeChangeLog::partialsOnly() recordPartial() only (expects('recordPartial'))
* - FakeChangeLog::any(id) both
* - FakeChangeLog::forbidden() nothing (a mock without expectations)
* A call that is not allowed throws – and, because the code under test often swallows
* change-log failures (FollowUp), it is also noted and fails the test in TestCase::tearDown()
* via verify().
*/
class FakeChangeLog extends ChangeLog
{
/**
* @var list<array{ability: string, object_type: string, object_id: string, summary: string,
* revision_id: ?int, snapshot_id: ?string, result: string}>
*/
public array $records = [];
/**
* @var list<array{ability: string, object_type: string, object_id: string, summary: string,
* revision_id: ?int}>
*/
public array $partials = [];
/** @var list<string> Calls that were not allowed, across all fakes of the running test. */
private static array $unexpected = [];
private static bool $used = false;
public function __construct(
private int $firstId = 1,
private ?Throwable $failure = null,
private bool $allowsRecord = true,
private bool $allowsPartial = false
) {
parent::__construct();
self::$used = true;
}
public static function forbidden(): self
{
return new self(1, null, false, false);
}
public static function partialsOnly(): self
{
return new self(1, null, false, true);
}
public static function any(int $firstId = 1): self
{
return new self($firstId, null, true, true);
}
/**
* Fails the running test if any fake got a call it does not allow; resets for the next test.
* Counts as one assertion in tests that created a fake (like a Mockery never() expectation).
*/
public static function verify(): void
{
$unexpected = self::$unexpected;
$used = self::$used;
self::$unexpected = [];
self::$used = false;
if ($used || $unexpected !== []) {
Assert::assertSame([], $unexpected, 'Unexpected change log calls: ' . implode('; ', $unexpected));
}
}
public function record(
string $ability,
string $objectType,
string $objectId,
string $summary,
?int $revisionId = null,
?string $snapshotId = null,
string $result = 'ok'
): ?int {
$this->refuseUnless($this->allowsRecord, sprintf('record(%s, %s, %s)', $ability, $objectType, $objectId));
$this->records[] = [
'ability' => $ability,
'object_type' => $objectType,
'object_id' => $objectId,
'summary' => $summary,
'revision_id' => $revisionId,
'snapshot_id' => $snapshotId,
'result' => $result,
];
if ($this->failure !== null) {
throw $this->failure;
}
return $this->firstId + count($this->records) - 1;
}
public function recordPartial(
string $ability,
string $objectType,
string $objectId,
string $summary,
?int $revisionId = null
): void {
$this->refuseUnless(
$this->allowsPartial,
sprintf('recordPartial(%s, %s, %s)', $ability, $objectType, $objectId)
);
$this->partials[] = [
'ability' => $ability,
'object_type' => $objectType,
'object_id' => $objectId,
'summary' => $summary,
'revision_id' => $revisionId,
];
}
/**
* Asserts the record() calls: one $expected per call, in order; each names only the
* arguments it checks (ability, object_type, object_id, summary, revision_id, …).
*
* @param array<string, mixed> ...$expected
*/
public function assertRecorded(array ...$expected): void
{
self::assertCalls('record()', $expected, $this->records);
}
/**
* Asserts the recordPartial() calls like assertRecorded().
*
* @param array<string, mixed> ...$expected
*/
public function assertPartials(array ...$expected): void
{
self::assertCalls('recordPartial()', $expected, $this->partials);
}
/**
* @param list<array<string, mixed>> $expected
* @param list<array<string, mixed>> $actual
*/
private static function assertCalls(string $method, array $expected, array $actual): void
{
Assert::assertCount(count($expected), $actual, 'Number of ChangeLog::' . $method . ' calls');
foreach ($expected as $i => $call) {
$checked = [];
foreach (array_keys($call) as $key) {
Assert::assertArrayHasKey($key, $actual[$i], 'Unknown ChangeLog argument');
$checked[$key] = $actual[$i][$key];
}
Assert::assertSame($call, $checked, 'ChangeLog::' . $method . ' call ' . $i);
}
}
/** @return list<string> The summaries of record(), in order. */
public function summaries(): array
{
return array_column($this->records, 'summary');
}
private function refuseUnless(bool $allowed, string $call): void
{
if (! $allowed) {
self::$unexpected[] = $call;
throw new LogicException('Unexpected ChangeLog::' . $call);
}
}
}