justReturn(['category', 'post_tag']); Functions\when('get_taxonomy')->justReturn((object) [ 'cap' => (object) ['assign_terms' => 'assign_categories', 'edit_terms' => 'manage_categories'], ]); Functions\when('current_user_can')->alias(static fn (string $cap): bool => match ($cap) { 'assign_categories' => $mayAssign, 'manage_categories' => $mayEditTerms, default => false, }); Functions\when('get_term')->alias(static fn (int $id) => $id === 4 ? new WP_Term(['term_id' => 4]) : null); Functions\when('term_exists')->alias( static fn (string $name) => $name === 'News' ? ['term_id' => '9', 'term_taxonomy_id' => '9'] : null ); } public function testResolvesIdsExistingNamesAndNewNames(): void { $this->stubTaxonomies(); $result = (new TermAssigner())->resolve('post', ['category' => [4, 'News', 'Neu']]); self::assertSame([], $result['errors']); self::assertSame(['category' => [4, 9, 'Neu']], $result['terms']); } public function testReportsForeignTaxonomyMissingIdsAndMissingRights(): void { $this->stubTaxonomies(false); $result = (new TermAssigner())->resolve('post', [ 'genre' => [1], 'category' => [77], 'post_tag' => ['Unbekannt'], ]); self::assertSame('Taxonomie gehört nicht zu diesem Inhaltstyp.', $result['errors']['genre']); self::assertSame('Begriff 77 existiert nicht.', $result['errors']['category']); self::assertStringContainsString('keine Berechtigung', $result['errors']['post_tag']); } public function testAssignRightsAreRequired(): void { $this->stubTaxonomies(true, false); $result = (new TermAssigner())->resolve('post', ['category' => [4]]); self::assertSame('Keine Berechtigung, Begriffe dieser Taxonomie zuzuweisen.', $result['errors']['category']); } public function testAssignCreatesNewTermsAndReplacesTheAssignment(): void { Functions\expect('wp_insert_term')->once()->with('Neu', 'category')->andReturn(['term_id' => 15]); Functions\expect('wp_set_object_terms')->once()->with(5, [4, 15], 'category', false)->andReturn([4, 15]); self::assertSame([15 => 'category'], (new TermAssigner())->assign(5, ['category' => [4, 'Neu']])); } public function testAFailedAssignmentDeletesTheTermsItCreated(): void { Functions\when('wp_insert_term')->justReturn(['term_id' => 15]); Functions\when('wp_set_object_terms')->justReturn(new WP_Error('x', 'kaputt')); Functions\expect('wp_delete_term')->once()->with(15, 'category')->andReturn(true); $this->expectException(RuntimeException::class); (new TermAssigner())->assign(5, ['category' => ['Neu']]); } public function testDeleteTermsAcceptsAlreadyMissingTermsAndThrowsOnRealFailures(): void { Functions\when('wp_delete_term')->alias(static fn (int $id): mixed => match ($id) { 15 => true, 16 => false, default => new WP_Error('x', 'kaputt'), }); $assigner = new TermAssigner(); $assigner->deleteTerms([15 => 'category', 16 => 'category']); $this->expectException(RuntimeException::class); $assigner->deleteTerms([15 => 'category', 17 => 'post_tag']); } public function testAssignFailureThrows(): void { Functions\when('wp_set_object_terms')->justReturn(new WP_Error('x', 'kaputt')); $this->expectException(RuntimeException::class); (new TermAssigner())->assign(5, ['category' => [4]]); } }