diff --git a/src/Search/Comb/Index.php b/src/Search/Comb/Index.php index d3003d93c65..9126b35fccb 100644 --- a/src/Search/Comb/Index.php +++ b/src/Search/Comb/Index.php @@ -94,7 +94,13 @@ public function delete($document) return; } - $data->forget($document->getSearchReference()); + $ref = $document->getSearchReference(); + + if (! $data->has($ref)) { + return; + } + + $data->forget($ref); $this->save($data); } diff --git a/tests/Search/CombIndexTest.php b/tests/Search/CombIndexTest.php index a41f086ac70..fa12fb837ac 100644 --- a/tests/Search/CombIndexTest.php +++ b/tests/Search/CombIndexTest.php @@ -4,15 +4,21 @@ use Illuminate\Contracts\Filesystem\Filesystem; use Mockery; +use PHPUnit\Framework\Attributes\Test; +use Statamic\Contracts\Search\Searchable; use Statamic\Search\Comb\Index; use Tests\TestCase; class CombIndexTest extends TestCase { - use IndexTests; + use IndexTests { + tearDown as protected indexTestsTearDown; + } private $fs; + private ?string $tmpDir = null; + public function setUp(): void { parent::setUp(); @@ -22,6 +28,18 @@ public function setUp(): void $this->instance('filesystem', $this->fs); } + public function tearDown(): void + { + if ($this->tmpDir && is_dir($this->tmpDir)) { + foreach (glob($this->tmpDir.'/*') as $file) { + @unlink($file); + } + @rmdir($this->tmpDir); + } + + $this->indexTestsTearDown(); + } + protected function beforeSearched() { $this->fs @@ -39,4 +57,70 @@ public function getIndexClass() { return Index::class; } + + #[Test] + public function delete_does_not_rewrite_the_index_when_the_reference_is_absent() + { + $path = $this->createIndexFile(['entry::a' => ['title' => 'Foo']]); + + $oldMtime = time() - 3600; + touch($path, $oldMtime); + clearstatcache(); + + $index = $this->getIndex('test', ['path' => $this->tmpDir], null); + + $document = Mockery::mock(Searchable::class); + $document->shouldReceive('getSearchReference')->andReturn('entry::missing'); + + $index->delete($document); + + clearstatcache(); + $this->assertSame( + $oldMtime, + filemtime($path), + 'Comb\Index::delete() rewrote the index file even though the reference was not present.' + ); + } + + #[Test] + public function delete_rewrites_the_index_when_the_reference_is_present() + { + $path = $this->createIndexFile([ + 'entry::a' => ['title' => 'Foo'], + 'entry::b' => ['title' => 'Bar'], + ]); + + $oldMtime = time() - 3600; + touch($path, $oldMtime); + clearstatcache(); + + $index = $this->getIndex('test', ['path' => $this->tmpDir], null); + + $document = Mockery::mock(Searchable::class); + $document->shouldReceive('getSearchReference')->andReturn('entry::a'); + + $index->delete($document); + + clearstatcache(); + $this->assertGreaterThan( + $oldMtime, + filemtime($path), + 'Comb\Index::delete() did not rewrite the index file even though the reference was present.' + ); + + $this->assertSame( + ['entry::b' => ['title' => 'Bar']], + json_decode(file_get_contents($path), true) + ); + } + + private function createIndexFile(array $data): string + { + $this->tmpDir = sys_get_temp_dir().'/statamic_comb_test_'.uniqid(); + mkdir($this->tmpDir, 0777, true); + $path = $this->tmpDir.'/test.json'; + file_put_contents($path, json_encode($data)); + + return $path; + } }