*/ private array $deleted = []; protected function setUp(): void { parent::setUp(); // The probe must work while Divi is untested – that is when it runs. ReleaseGate::useResolver(static fn (): ReleaseStatus => ReleaseMatrices::uniform(ReleaseState::Untested)); $this->deleted = []; Functions\when('wp_insert_post')->justReturn(41); Functions\when('get_post')->justReturn( new WP_Post(['ID' => 41, 'post_content' => '']) ); Functions\when('get_post_meta')->alias( static fn (int $id, string $key): string => $key === '_et_pb_show_page_creation' ? 'off' : 'on' ); Functions\when('wp_delete_post')->alias(function (int $id, bool $force): WP_Post { self::assertTrue($force, 'never to the trash'); $this->deleted[] = $id; return new WP_Post(['ID' => $id]); }); } private function composer(?callable $write = null): ContentComposer { $composer = Mockery::mock(ContentComposer::class); $default = static function (int $id, string $type, array $sections): array { // The composer writes through the saver, which asks the gate: the probe must have opened it for 41. ReleaseGate::assertFeatures(DiviFeature::cases(), $id); return ['warnings' => [], 'outline' => '']; }; $composer->allows('write')->andReturnUsing($write ?? $default); return $composer; } private function renderer(string $html): PageRenderer { $renderer = Mockery::mock(PageRenderer::class); $renderer->allows('render')->andReturn($html); return $renderer; } public function testGreenWhenEveryModuleRendersAndThePageIsGone(): void { $probe = new WriteProbe( $this->composer(), $this->renderer(implode(' ', WriteProbe::TEXTS)), '/nonexistent-divi' ); self::assertSame([], $probe->run()); self::assertSame([41], $this->deleted); } public function testMissingTextsAndFlagsAreReported(): void { Functions\when('get_post_meta')->justReturn(''); $probe = new WriteProbe($this->composer(), $this->renderer('litcheck-heading'), '/nonexistent-divi'); $problems = $probe->run(); self::assertContains('Builder-Kennzeichen _et_pb_use_builder fehlt.', $problems); self::assertContains('„litcheck-button“ fehlt in der gerenderten Seite.', $problems); self::assertNotContains('„litcheck-heading“ fehlt in der gerenderten Seite.', $problems); self::assertSame([41], $this->deleted); } public function testDeletesThePageEvenWhenWritingThrows(): void { $composer = $this->composer(static function (): never { throw new RuntimeException('Composer kaputt'); }); $log = (string) tempnam(sys_get_temp_dir(), 'lit-mcp-log'); $previous = ini_set('error_log', $log); try { $problems = (new WriteProbe($composer, $this->renderer(''), '/nonexistent-divi'))->run(); $logged = (string) file_get_contents($log); } finally { ini_set('error_log', (string) $previous); unlink($log); } self::assertSame(['Schreibtest abgebrochen (Details im PHP-Error-Log).'], $problems); self::assertStringContainsString('Composer kaputt', $logged); self::assertSame([41], $this->deleted); $this->expectException(\LIT\MCP\Integrations\Divi\Release\DiviLocked::class); ReleaseGate::assertFeature(DiviFeature::PageWrite, 41); } public function testReportsAPageThatCouldNotBeDeleted(): void { Functions\when('wp_delete_post')->justReturn(false); $probe = new WriteProbe( $this->composer(), $this->renderer(implode(' ', WriteProbe::TEXTS)), '/nonexistent-divi' ); $problems = $probe->run(); self::assertSame( ['Die Testseite 41 konnte nicht gelöscht werden; bitte unter Seiten endgültig löschen.'], $problems ); } public function testCreatesAMarkedDraftPage(): void { // wp_insert_post is already stubbed in setUp(); Functions\expect() cannot rewire the same // function again (Brain Monkey keeps the first redefinition), so the args are captured here. $captured = null; Functions\when('wp_insert_post')->alias(static function (array $post, bool $wpError) use (&$captured): int { $captured = $post; return 41; }); $probe = new WriteProbe( $this->composer(), $this->renderer(implode(' ', WriteProbe::TEXTS)), '/nonexistent-divi' ); $probe->run(); self::assertIsArray($captured); self::assertSame('page', $captured['post_type']); self::assertSame('draft', $captured['post_status']); self::assertTrue(str_starts_with($captured['post_title'], '[LIT MCP Prüfung] ')); self::assertSame(['_lit_mcp_check_page' => '1'], $captured['meta_input']); } public function testRemovesOrphanedCheckPagesThroughTheSharedCleanup(): void { Functions\expect('get_posts')->once()->with(Mockery::on(static fn (array $query): bool => $query['meta_key'] === '_lit_mcp_check_page' && $query['post_type'] === 'page' && $query['post_status'] === 'draft' && ($query['date_query'][0]['column'] ?? null) === 'post_modified_gmt'))->andReturn([ new WP_Post(['ID' => 51, 'post_title' => WriteProbe::TITLE . ' 2026-01-01 00:00']), new WP_Post(['ID' => 60, 'post_title' => 'Ein anderer Entwurf']), ]); self::assertSame([51], WriteProbe::removeOrphans()); self::assertSame([51], $this->deleted); } public function testAPageThatCannotBeCreatedIsAFixedTextWithoutTheWordPressMessage(): void { Functions\when('wp_insert_post')->justReturn(new \WP_Error('x', 'Datenbank voll')); $problems = (new WriteProbe($this->composer(), $this->renderer(''), '/nonexistent-divi'))->run(); self::assertSame(['Die Testseite konnte nicht angelegt werden (Details im PHP-Error-Log).'], $problems); self::assertSame([], $this->deleted); } }