test: AssertsToolErrors helper for tool error assertions (T3)

tests/Support/AssertsToolErrors (used by TestCase): assertToolError($result, code)
checks the WP_Error code lit_<code> and the code of the client JSON and returns the
payload; clientError() decodes the payload; errorFields() returns the fields and
asserts they exist. Replaces the private clientError() decoders of
DiviWriteAbilitiesTest/DiviReplaceTextAbilityTest, 216 single-line
assertSame('lit_…', …->get_error_code()) assertions, 24 get_error_data()['fields']
digs and two json_decode(message) reads. ErrorsTest keeps json_decode: it tests
the format itself. Tests 1404 -> 1409 (AssertsToolErrorsTest); no test has fewer
assertions.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-26 17:55:35 +02:00
co-authored by Claude Opus 5.5
parent 72eeaa46ad
commit 60290c85a5
50 changed files with 337 additions and 256 deletions
@@ -76,7 +76,7 @@ final class ContentCreateAbilityTest extends TestCase
$result = (new ContentCreateAbility($this->schema(), $writer, FakeChangeLog::forbidden()))
->execute(['post_type' => 'lit_person', 'title' => 'Anna']);
self::assertSame('lit_validation_failed', $result->get_error_code());
self::assertToolError($result, 'validation_failed');
}
public function testCreatesADraftWritesExtrasLogsAndReturnsAToolResult(): void
@@ -131,7 +131,7 @@ final class ContentCreateAbilityTest extends TestCase
self::assertTrue($failure->rolledBack);
self::assertStringContainsString('Pods could not save the fields.', $failure->getMessage());
self::assertSame('lit_write_failed', $failure->toError()->get_error_code());
self::assertToolError($failure->toError(), 'write_failed');
}
public function testMetaFieldsTheUserMayNotAddAreRefusedAndTheDraftIsRemoved(): void
@@ -149,8 +149,8 @@ final class ContentCreateAbilityTest extends TestCase
$result = (new ContentCreateAbility($this->schema(), $writer, $log))->execute(self::INPUT);
self::assertSame('lit_validation_failed', $result->get_error_code());
self::assertArrayHasKey('fields.city', $result->get_error_data()['fields']);
self::assertToolError($result, 'validation_failed');
self::assertArrayHasKey('fields.city', self::errorFields($result));
}
public function testARefusedDraftThatCannotBeDeletedIsAPartialWrite(): void
@@ -219,7 +219,7 @@ final class ContentCreateAbilityTest extends TestCase
self::assertFalse($failure->rolledBack);
self::assertNull($failure->postId);
self::assertSame('lit_partial_write', $failure->toError()->get_error_code());
self::assertToolError($failure->toError(), 'partial_write');
$log->assertPartials(['ability' => 'lit/content-create', 'object_type' => 'lit_person', 'object_id' => '']);
}
@@ -261,6 +261,6 @@ final class ContentCreateAbilityTest extends TestCase
self::assertTrue($ability->permission(['post_type' => 'page']));
Functions\when('current_user_can')->justReturn(false);
self::assertSame('lit_forbidden', $ability->permission(['post_type' => 'page'])->get_error_code());
self::assertToolError($ability->permission(['post_type' => 'page']), 'forbidden');
}
}
@@ -128,14 +128,14 @@ final class ContentCreateDiviTest extends TestCase
Functions\expect('wp_insert_post')->never();
$inactive = $this->ability($this->composer(false))->execute(self::INPUT);
self::assertSame('lit_unsupported', $inactive->get_error_code());
self::assertToolError($inactive, 'unsupported');
$none = $this->ability(null)->execute(self::INPUT);
self::assertSame('lit_unsupported', $none->get_error_code());
self::assertToolError($none, 'unsupported');
$both = $this->ability($this->composer())->execute(self::INPUT + ['content' => '<p>x</p>']);
self::assertSame('lit_validation_failed', $both->get_error_code());
self::assertArrayHasKey('content', $both->get_error_data()['fields']);
self::assertToolError($both, 'validation_failed');
self::assertArrayHasKey('content', self::errorFields($both));
}
public function testEveryValidationErrorComesBackAndNothingIsCreated(): void
@@ -149,10 +149,10 @@ final class ContentCreateDiviTest extends TestCase
$error = $this->ability($composer, $writer)->execute(self::INPUT);
self::assertSame('lit_invalid_attr_path', $error->get_error_code());
self::assertToolError($error, 'invalid_attr_path');
self::assertSame(
['fields.role' => 'Pflichtfeld fehlt.', 'divi[0].attrs.x' => 'unbekannt'],
$error->get_error_data()['fields']
self::errorFields($error)
);
}
@@ -181,8 +181,8 @@ final class ContentCreateDiviTest extends TestCase
$error = $this->ability($composer)->execute(self::INPUT);
self::assertInstanceOf(WP_Error::class, $error);
self::assertSame('lit_validation_failed', $error->get_error_code());
self::assertArrayHasKey('divi[0].modules[0].text', $error->get_error_data()['fields']);
self::assertToolError($error, 'validation_failed');
self::assertArrayHasKey('divi[0].modules[0].text', self::errorFields($error));
}
public function testARefusedLayoutWhoseDraftCannotBeDeletedIsAPartialWrite(): void
@@ -254,8 +254,8 @@ final class ContentCreateDiviTest extends TestCase
'attrs' => ['module.meta.adminLabel' => ['desktop' => 'Lang']],
'modules' => [['type' => 'button', 'text' => 'Los', 'attrs' => ['button.innerContent' => ['desktop' => ['text' => 'x']]]]],
]]]);
self::assertSame('lit_validation_failed', $overlap->get_error_code());
$fields = $overlap->get_error_data()['fields'];
self::assertToolError($overlap, 'validation_failed');
$fields = self::errorFields($overlap);
self::assertArrayHasKey('divi[0].attrs.module.meta.adminLabel', $fields, 'S41: label and attrs label');
self::assertArrayHasKey('divi[0].modules[0].modules[0][0].attrs.button.innerContent', $fields, 'S40');
self::assertSame([], $saved);
@@ -47,7 +47,7 @@ final class ContentGetAbilityTest extends TestCase
$schema = Mockery::mock(ContentSchema::class);
$ability = new ContentGetAbility($schema, Mockery::mock(PostPresenter::class));
self::assertSame('lit_not_found', $ability->permission(['post_id' => 99])->get_error_code());
self::assertToolError($ability->permission(['post_id' => 99]), 'not_found');
}
public function testOutputSchemaAllowsNullContentForDivi(): void
@@ -96,7 +96,7 @@ final class ContentQueryAbilityTest extends TestCase
{
$result = $this->ability()->execute(['post_type' => 'page', 'cursor' => '???']);
self::assertSame('lit_validation_failed', $result->get_error_code());
self::assertToolError($result, 'validation_failed');
}
public function testQueryIsRestrictedToOwnPostsWithoutEditOthersPosts(): void
@@ -193,7 +193,7 @@ final class ContentQueryAbilityTest extends TestCase
$result = $this->ability($schema)->execute(['post_type' => 'page', 'field' => ['key' => 'ghost']]);
self::assertSame('lit_validation_failed', $result->get_error_code());
self::assertToolError($result, 'validation_failed');
self::assertStringContainsString('role', $result->get_error_message());
}
@@ -209,9 +209,9 @@ final class ContentQueryAbilityTest extends TestCase
$password = $this->ability($schema)->execute(['post_type' => 'page', 'field' => ['key' => 'secret']]);
$protected = $this->ability($schema)->execute(['post_type' => 'page', 'field' => ['key' => '_internal']]);
self::assertSame('lit_validation_failed', $password->get_error_code());
self::assertToolError($password, 'validation_failed');
self::assertSame('Verfügbare Felder: role.', $password->get_error_data()['hint']);
self::assertSame('lit_validation_failed', $protected->get_error_code());
self::assertToolError($protected, 'validation_failed');
self::assertSame('Verfügbare Felder: role.', $protected->get_error_data()['hint']);
}
@@ -225,7 +225,7 @@ final class ContentQueryAbilityTest extends TestCase
'taxonomy' => ['taxonomy' => 'ghost', 'terms' => [1]],
]);
self::assertSame('lit_validation_failed', $result->get_error_code());
self::assertToolError($result, 'validation_failed');
self::assertStringContainsString('category', $result->get_error_message());
}
@@ -235,7 +235,7 @@ final class ContentQueryAbilityTest extends TestCase
$schema->allows('isManaged')->andReturn(false);
$ability = new ContentQueryAbility($schema, Mockery::mock(PostPresenter::class));
self::assertSame('lit_not_found', $ability->permission(['post_type' => 'secret'])->get_error_code());
self::assertToolError($ability->permission(['post_type' => 'secret']), 'not_found');
}
public function testPermissionIsForbiddenWithoutEditRights(): void
@@ -245,7 +245,7 @@ final class ContentQueryAbilityTest extends TestCase
$schema = SchemaFixture::managed();
$ability = new ContentQueryAbility($schema, Mockery::mock(PostPresenter::class));
self::assertSame('lit_forbidden', $ability->permission(['post_type' => 'page'])->get_error_code());
self::assertToolError($ability->permission(['post_type' => 'page']), 'forbidden');
}
public function testInputSchemaLimitsSearchLengthAndTaxonomyTermsCount(): void
@@ -65,7 +65,7 @@ final class ContentTypesAbilityTest extends TestCase
$result = (new ContentTypesAbility($schema))->execute(['post_type' => 'nope']);
self::assertInstanceOf(WP_Error::class, $result);
self::assertSame('lit_not_found', $result->get_error_code());
self::assertToolError($result, 'not_found');
self::assertStringContainsString('page, post', $result->get_error_message());
}
@@ -95,7 +95,7 @@ final class ContentTypesAbilityTest extends TestCase
$result = (new ContentTypesAbility($schema))->execute(['post_type' => 'page']);
self::assertInstanceOf(WP_Error::class, $result);
self::assertSame('lit_forbidden', $result->get_error_code());
self::assertToolError($result, 'forbidden');
}
public function testPermissionRequiresEditPosts(): void
@@ -103,7 +103,7 @@ final class ContentTypesAbilityTest extends TestCase
Functions\when('current_user_can')->justReturn(false);
$ability = new ContentTypesAbility(Mockery::mock(ContentSchema::class));
self::assertSame('lit_forbidden', $ability->permission([])->get_error_code());
self::assertToolError($ability->permission([]), 'forbidden');
}
public function testPermissionForbidsANonEditableSpecificType(): void
@@ -115,7 +115,7 @@ final class ContentTypesAbilityTest extends TestCase
$result = (new ContentTypesAbility($schema))->permission(['post_type' => 'lit_person']);
self::assertInstanceOf(WP_Error::class, $result);
self::assertSame('lit_forbidden', $result->get_error_code());
self::assertToolError($result, 'forbidden');
}
public function testPermissionAllowsAnEditableSpecificType(): void
@@ -71,7 +71,7 @@ final class ContentUpdateAbilityTest extends TestCase
$result = $this->ability(Mockery::mock(PostWriter::class))
->execute(['post_id' => 5, 'rev' => Rev::ofPost($this->post), 'title' => 'Neu']);
self::assertSame('lit_validation_failed', $result->get_error_code());
self::assertToolError($result, 'validation_failed');
}
public function testAMissedScheduleIsRefusedBecauseSavingWouldPublish(): void
@@ -84,8 +84,8 @@ final class ContentUpdateAbilityTest extends TestCase
$result = $this->ability(Mockery::mock(PostWriter::class))
->execute(['post_id' => 5, 'rev' => Rev::ofPost($this->post), 'title' => 'Neu']);
self::assertSame('lit_validation_failed', $result->get_error_code());
self::assertArrayHasKey('post_id', $result->get_error_data()['fields']);
self::assertToolError($result, 'validation_failed');
self::assertArrayHasKey('post_id', self::errorFields($result));
self::assertStringContainsString('lit/post-schedule', $result->get_error_data()['hint']);
}
@@ -96,7 +96,7 @@ final class ContentUpdateAbilityTest extends TestCase
$result = $this->ability(Mockery::mock(PostWriter::class))
->execute(['post_id' => 5, 'rev' => 'stale', 'title' => 'Neu']);
self::assertSame('lit_rev_mismatch', $result->get_error_code());
self::assertToolError($result, 'rev_mismatch');
self::assertStringContainsString('lit-content-get', $result->get_error_data()['hint']);
}
@@ -105,7 +105,7 @@ final class ContentUpdateAbilityTest extends TestCase
$result = $this->ability(Mockery::mock(PostWriter::class))
->execute(['post_id' => 5, 'rev' => Rev::of('<p>Alt</p>'), 'title' => 'Neu']);
self::assertSame('lit_rev_mismatch', $result->get_error_code());
self::assertToolError($result, 'rev_mismatch');
}
public function testRawContentOnDiviPagesIsUnsupported(): void
@@ -117,7 +117,7 @@ final class ContentUpdateAbilityTest extends TestCase
$result = $this->ability($writer)
->execute(['post_id' => 5, 'rev' => Rev::ofPost($this->post), 'content' => '<p>Neu</p>']);
self::assertSame('lit_unsupported', $result->get_error_code());
self::assertToolError($result, 'unsupported');
self::assertStringContainsString(PostPresenter::DIVI_INACTIVE_NOTE, $result->get_error_data()['hint']);
}
@@ -129,7 +129,7 @@ final class ContentUpdateAbilityTest extends TestCase
$result = $this->ability($writer)->execute(['post_id' => 5, 'rev' => Rev::ofPost($this->post)]);
self::assertSame('lit_validation_failed', $result->get_error_code());
self::assertToolError($result, 'validation_failed');
}
public function testUpdateKeepsTheStatusAndCapturesARestorePointFirst(): void
@@ -236,7 +236,7 @@ final class ContentUpdateAbilityTest extends TestCase
$failure = $this->executeExpectingFailure($writer, $input);
self::assertTrue($failure->rolledBack);
self::assertSame('lit_write_failed', $failure->toError()->get_error_code());
self::assertToolError($failure->toError(), 'write_failed');
$message = $failure->toError()->get_error_message();
self::assertStringNotContainsString('Nichts wurde gespeichert', $message);
self::assertStringContainsString('Die Änderung wurde nicht gespeichert (Revision 811 bleibt erhalten).', $message);
@@ -326,7 +326,7 @@ final class ContentUpdateAbilityTest extends TestCase
self::assertFalse($failure->rolledBack);
self::assertStringContainsString('restore kaputt', $failure->getMessage());
$error = $failure->toError();
self::assertSame('lit_partial_write', $error->get_error_code());
self::assertToolError($error, 'partial_write');
self::assertSame(5, $error->get_error_data()['post_id']);
self::assertSame(811, $error->get_error_data()['revision_id']);
$log->assertPartials([
@@ -48,8 +48,8 @@ final class PostWriterTest extends TestCase
'template' => 'fehlt.php',
], true);
$fields = $result->get_error_data()['fields'];
self::assertSame('lit_validation_failed', $result->get_error_code());
$fields = self::errorFields($result);
self::assertToolError($result, 'validation_failed');
self::assertSame('Pflichtfeld fehlt.', $fields['fields.role']);
self::assertSame('Taxonomie gehört nicht zu diesem Inhaltstyp.', $fields['terms.genre']);
self::assertSame('Inhaltstyp unterstützt kein Beitragsbild.', $fields['featured_image']);
@@ -72,7 +72,7 @@ final class PostWriterTest extends TestCase
$result = $writer->prepare('page', $input, false);
self::assertFalse(is_array($result), $path . ' should be rejected');
return (string) $result->get_error_data()['fields'][$path];
return (string) self::errorFields($result)[$path];
}
public function testParentMustBeAReadableNonDescendantEntryOfAHierarchicalType(): void
@@ -152,7 +152,7 @@ final class PostWriterTest extends TestCase
false
);
self::assertSame(['fields.secret' => 'Keine Berechtigung, dieses Feld zu ändern.'], $result->get_error_data()['fields']);
self::assertSame(['fields.secret' => 'Keine Berechtigung, dieses Feld zu ändern.'], self::errorFields($result));
self::assertSame([['edit_post_meta', 5, 'city'], ['edit_post_meta', 5, 'secret']], $checked);
}