|
| 1 | +<?php |
| 2 | + |
| 3 | +declare(strict_types=1); |
| 4 | + |
| 5 | +namespace { |
| 6 | + // include/manual-lookup.inc defines global functions and depends on the global |
| 7 | + // get_manual_search_sections(). That lives in include/site.inc, which cannot be |
| 8 | + // required in isolation, so repeat the list from site.inc here. |
| 9 | + if (!function_exists('get_manual_search_sections')) { |
| 10 | + /** @return list<string> */ |
| 11 | + function get_manual_search_sections(): array |
| 12 | + { |
| 13 | + return [ |
| 14 | + "", "book.", "ref.", "function.", "class.", "enum.", |
| 15 | + "features.", "control-structures.", "language.", |
| 16 | + "about.", "faq.", |
| 17 | + ]; |
| 18 | + } |
| 19 | + } |
| 20 | + |
| 21 | + require_once phpweb\ProjectGlobals::getProjectRoot() . '/include/manual-lookup.inc'; |
| 22 | +} |
| 23 | + |
| 24 | +namespace phpweb\Test\Unit\ManualLookup { |
| 25 | + |
| 26 | + use phpweb\ProjectGlobals; |
| 27 | + use PHPUnit\Framework; |
| 28 | + |
| 29 | + #[Framework\Attributes\CoversFunction('find_manual_page')] |
| 30 | + #[Framework\Attributes\CoversFunction('find_manual_page_slow')] |
| 31 | + #[Framework\Attributes\RunTestsInSeparateProcesses] |
| 32 | + #[Framework\Attributes\PreserveGlobalState(false)] |
| 33 | + final class FindManualPageTest extends Framework\TestCase |
| 34 | + { |
| 35 | + // Manual pages checked into public/manual/en/ that the searches below resolve to. |
| 36 | + private const SLOW_PATH_PAGE = '/manual/en/function.strpos.php'; |
| 37 | + |
| 38 | + private const FAST_PATH_PAGE = '/manual/en/function.rtrim.php'; |
| 39 | + |
| 40 | + private string $database; |
| 41 | + |
| 42 | + protected function setUp(): void |
| 43 | + { |
| 44 | + $this->database = ProjectGlobals::getBackendRoot() . '/manual-lookup.sqlite'; |
| 45 | + |
| 46 | + // A database here means a live checkout with an rsynced manual, not a test one |
| 47 | + if (file_exists($this->database)) { |
| 48 | + self::markTestSkipped('manual-lookup.sqlite is present, refusing to overwrite it'); |
| 49 | + } |
| 50 | + } |
| 51 | + |
| 52 | + protected function tearDown(): void |
| 53 | + { |
| 54 | + @unlink($this->database); |
| 55 | + } |
| 56 | + |
| 57 | + /** |
| 58 | + * Regression test for the production fatal: |
| 59 | + * Uncaught PDOException: SQLSTATE[HY000]: General error: 8 |
| 60 | + * attempt to write a readonly database in include/manual-lookup.inc |
| 61 | + * |
| 62 | + * A read-only, locked or truncated database must fall back to the slow |
| 63 | + * search rather than throwing. |
| 64 | + */ |
| 65 | + public function testFallsBackToSlowSearchWhenSqliteQueryFails(): void |
| 66 | + { |
| 67 | + file_put_contents($this->database, 'this is not a sqlite database'); |
| 68 | + |
| 69 | + self::assertSame(self::SLOW_PATH_PAGE, find_manual_page('en', 'strpos')); |
| 70 | + } |
| 71 | + |
| 72 | + public function testFallsBackToSlowSearchForDottedKeywordWhenSqliteQueryFails(): void |
| 73 | + { |
| 74 | + // A dotted keyword takes the other SQL branch, which must fall back too |
| 75 | + file_put_contents($this->database, 'this is not a sqlite database'); |
| 76 | + |
| 77 | + self::assertSame(self::SLOW_PATH_PAGE, find_manual_page('en', 'function.strpos')); |
| 78 | + } |
| 79 | + |
| 80 | + public function testFallsBackToSlowSearchWhenNoDatabasePresent(): void |
| 81 | + { |
| 82 | + self::assertSame(self::SLOW_PATH_PAGE, find_manual_page('en', 'strpos')); |
| 83 | + } |
| 84 | + |
| 85 | + /** |
| 86 | + * The fast path maps the keyword to a different page than the slow search |
| 87 | + * would find, so a match on it proves the database was really used. |
| 88 | + */ |
| 89 | + #[Framework\Attributes\RequiresPhpExtension('pdo_sqlite')] |
| 90 | + public function testUsesSqliteFastPathWhenDatabaseIsValid(): void |
| 91 | + { |
| 92 | + $dbh = new \PDO('sqlite:' . $this->database); |
| 93 | + $dbh->exec('CREATE TABLE fs (lang TEXT, prefix TEXT, keyword TEXT, name TEXT, prio INT)'); |
| 94 | + $dbh->exec(sprintf( |
| 95 | + "INSERT INTO fs (lang, prefix, keyword, name, prio) VALUES ('en', 'function.', 'strpos', '%s', 3)", |
| 96 | + self::FAST_PATH_PAGE, |
| 97 | + )); |
| 98 | + $dbh = null; |
| 99 | + |
| 100 | + self::assertSame(self::FAST_PATH_PAGE, find_manual_page('en', 'strpos')); |
| 101 | + } |
| 102 | + } |
| 103 | +} |
0 commit comments