engine = new DatabaseFulltextEngine; // 픽스처 테이블(test_table)은 실존하지 않으므로 인덱스 카탈로그를 시드해 // MATCH 조립 경로를 검증한다 — 시드하지 않으면 인덱스 부재 게이트(#103)가 // LIKE 로 내려 보내 조립 단언이 무의미해진다. DatabaseFulltextEngine::primeFulltextIndexCatalog([ 'test_table' => [['name']], ]); } protected function tearDown(): void { DatabaseFulltextEngine::forgetFulltextIndexCatalog(); parent::tearDown(); } /** * update()가 no-op으로 동작하는지 테스트합니다. */ public function test_update_is_noop(): void { $models = new Collection; // 예외 없이 정상 실행되어야 함 $this->engine->update($models); $this->assertTrue(true); } /** * delete()가 no-op으로 동작하는지 테스트합니다. */ public function test_delete_is_noop(): void { $models = new Collection; $this->engine->delete($models); $this->assertTrue(true); } /** * flush()가 no-op으로 동작하는지 테스트합니다. */ public function test_flush_is_noop(): void { $model = Mockery::mock(Model::class); $this->engine->flush($model); $this->assertTrue(true); } /** * createIndex()가 no-op으로 동작하는지 테스트합니다. */ public function test_create_index_is_noop(): void { $this->engine->createIndex('test_index'); $this->assertTrue(true); } /** * deleteIndex()가 no-op으로 동작하는지 테스트합니다. */ public function test_delete_index_is_noop(): void { $this->engine->deleteIndex('test_index'); $this->assertTrue(true); } /** * 빈 검색어로 검색 시 빈 결과를 반환하는지 테스트합니다. */ public function test_search_returns_empty_for_blank_query(): void { $model = $this->createSearchableModel(); $builder = new Builder($model, ' '); $results = $this->engine->search($builder); $this->assertNull($results['query']); $this->assertEquals(0, $results['total']); } /** * 빈 검색어로 검색 시 getTotalCount가 0을 반환하는지 테스트합니다. */ public function test_get_total_count_returns_zero_for_empty_results(): void { $results = ['total' => 0]; $this->assertEquals(0, $this->engine->getTotalCount($results)); } /** * getTotalCount가 올바른 값을 반환하는지 테스트합니다. */ public function test_get_total_count_returns_correct_value(): void { $results = ['total' => 42]; $this->assertEquals(42, $this->engine->getTotalCount($results)); } /** * mapIds가 빈 결과에 대해 빈 컬렉션을 반환하는지 테스트합니다. */ public function test_map_ids_returns_empty_collection_for_null_query(): void { $results = ['query' => null]; $ids = $this->engine->mapIds($results); $this->assertInstanceOf(\Illuminate\Support\Collection::class, $ids); $this->assertTrue($ids->isEmpty()); } /** * map()이 빈 결과에 대해 빈 컬렉션을 반환하는지 테스트합니다. */ public function test_map_returns_empty_collection_for_null_query(): void { $model = $this->createSearchableModel(); $builder = new Builder($model, 'test'); $results = ['query' => null]; $mapped = $this->engine->map($builder, $results, $model); $this->assertInstanceOf(Collection::class, $mapped); $this->assertTrue($mapped->isEmpty()); } /** * lazyMap()이 빈 결과에 대해 빈 LazyCollection을 반환하는지 테스트합니다. */ public function test_lazy_map_returns_empty_for_null_query(): void { $model = $this->createSearchableModel(); $builder = new Builder($model, 'test'); $results = ['query' => null]; $mapped = $this->engine->lazyMap($builder, $results, $model); $this->assertInstanceOf(LazyCollection::class, $mapped); $this->assertTrue($mapped->isEmpty()); } /** * FulltextSearchable 미구현 모델로 검색 시 빈 결과를 반환하는지 테스트합니다. */ public function test_search_returns_empty_for_non_fulltext_searchable_model(): void { $model = new NonFulltextModel; $builder = new Builder($model, '검색어'); $results = $this->engine->search($builder); $this->assertNull($results['query']); $this->assertEquals(0, $results['total']); } /** * searchableColumns()가 빈 배열인 모델로 검색 시 빈 결과를 반환하는지 테스트합니다. */ public function test_search_returns_empty_for_empty_searchable_columns(): void { $model = $this->createSearchableModelWithEmptyColumns(); $builder = new Builder($model, '검색어'); $results = $this->engine->search($builder); $this->assertNull($results['query']); $this->assertEquals(0, $results['total']); } /** * supportsFulltext()가 MySQL/MariaDB에서 true를 반환하는지 테스트합니다. */ public function test_supports_fulltext_returns_true_for_mysql(): void { // 현재 테스트 환경의 드라이버를 확인 $driver = DB::getDriverName(); if (in_array($driver, ['mysql', 'mariadb'])) { $this->assertTrue(DatabaseFulltextEngine::supportsFulltext()); } else { $this->assertFalse(DatabaseFulltextEngine::supportsFulltext()); } } /** * supportsNgramParser()가 MySQL에서만 true를 반환하는지 테스트합니다. * MariaDB를 mysql 드라이버로 연결한 경우에도 false를 반환해야 합니다. */ public function test_supports_ngram_parser_returns_true_only_for_mysql(): void { $driver = DB::getDriverName(); if ($driver === 'mariadb') { $this->assertFalse(DatabaseFulltextEngine::supportsNgramParser()); } elseif ($driver === 'mysql') { // mysql 드라이버라도 실제 MariaDB이면 false if (DatabaseFulltextEngine::isMariaDb()) { $this->assertFalse(DatabaseFulltextEngine::supportsNgramParser()); } else { $this->assertTrue(DatabaseFulltextEngine::supportsNgramParser()); } } else { $this->assertFalse(DatabaseFulltextEngine::supportsNgramParser()); } } /** * isMariaDb()가 mariadb 드라이버에서 true를 반환하는지 테스트합니다. */ public function test_is_mariadb_detects_mariadb_driver(): void { $driver = DB::getDriverName(); if ($driver === 'mariadb') { $this->assertTrue(DatabaseFulltextEngine::isMariaDb()); } elseif ($driver === 'mysql') { // mysql 드라이버로 MariaDB에 연결한 경우 서버 버전으로 감지 $version = DB::selectOne('SELECT VERSION() as version'); $isActuallyMariaDb = $version && str_contains(strtolower($version->version), 'mariadb'); $this->assertEquals($isActuallyMariaDb, DatabaseFulltextEngine::isMariaDb()); } else { $this->assertFalse(DatabaseFulltextEngine::isMariaDb()); } } /** * whereFulltext()가 FULLTEXT 지원 DBMS에서 MATCH...AGAINST를 생성하는지 테스트합니다. */ public function test_where_fulltext_generates_match_against_on_mysql(): void { $driver = DB::getDriverName(); if (! in_array($driver, ['mysql', 'mariadb'])) { $this->markTestSkipped('FULLTEXT 미지원 DBMS에서는 LIKE fallback 테스트로 대체'); } $model = $this->createSearchableModel(); $query = $model->newQuery(); DatabaseFulltextEngine::whereFulltext($query, 'name', '검색어'); $sql = $query->toSql(); $this->assertStringContainsString('MATCH(`name`) AGAINST(? IN BOOLEAN MODE)', $sql); } /** * whereFulltext()가 OR 조건을 올바르게 생성하는지 테스트합니다. */ public function test_where_fulltext_generates_or_condition(): void { $driver = DB::getDriverName(); if (! in_array($driver, ['mysql', 'mariadb'])) { $this->markTestSkipped('FULLTEXT 미지원 DBMS'); } $model = $this->createSearchableModel(); $query = $model->newQuery()->where('id', 1); DatabaseFulltextEngine::whereFulltext($query, 'name', '검색어', 'or'); $sql = $query->toSql(); $this->assertStringContainsString('MATCH(`name`) AGAINST(? IN BOOLEAN MODE)', $sql); } /** * whereFulltext()가 FULLTEXT 미지원 DBMS에서 LIKE fallback을 생성하는지 테스트합니다. */ public function test_where_fulltext_like_fallback_on_non_fulltext_dbms(): void { $driver = DB::getDriverName(); if (in_array($driver, ['mysql', 'mariadb'])) { $this->markTestSkipped('FULLTEXT 지원 DBMS에서는 MATCH 테스트로 대체'); } $model = $this->createSearchableModel(); $query = $model->newQuery(); DatabaseFulltextEngine::whereFulltext($query, 'name', '검색어'); $sql = $query->toSql(); $this->assertStringContainsString('LIKE', $sql); } /** * addFulltextIndex()가 FULLTEXT 미지원 DBMS에서 예외 없이 스킵하는지 테스트합니다. */ public function test_add_fulltext_index_skips_on_non_fulltext_dbms(): void { $driver = DB::getDriverName(); if (in_array($driver, ['mysql', 'mariadb'])) { $this->markTestSkipped('FULLTEXT 지원 DBMS에서는 스킵 테스트 불가'); } // 예외 없이 정상 스킵되어야 함 DatabaseFulltextEngine::addFulltextIndex('non_existent_table', 'ft_test', 'name'); $this->assertTrue(true); } /** * sanitizeBooleanModeKeyword()가 BOOLEAN MODE 연산자를 제거하고 phrase로 변환하는지 테스트합니다. */ public function test_sanitize_boolean_mode_keyword_wraps_tokens_in_phrases(): void { $this->assertSame('"셔츠"', DatabaseFulltextEngine::sanitizeBooleanModeKeyword('셔츠')); $this->assertSame('"shirt"', DatabaseFulltextEngine::sanitizeBooleanModeKeyword('shirt')); // 다중 토큰 → 각각 phrase $this->assertSame('"셔츠" "면"', DatabaseFulltextEngine::sanitizeBooleanModeKeyword('셔츠 면')); } /** * sanitizeBooleanModeKeyword()가 각 BOOLEAN MODE 연산자를 제거하는지 테스트합니다. */ public function test_sanitize_boolean_mode_keyword_strips_operators(): void { // 연산자가 제거되고 남은 토큰만 phrase 로 묶임 $this->assertSame('"상품"', DatabaseFulltextEngine::sanitizeBooleanModeKeyword('+상품')); $this->assertSame('"바지"', DatabaseFulltextEngine::sanitizeBooleanModeKeyword('-바지')); $this->assertSame('"상품"', DatabaseFulltextEngine::sanitizeBooleanModeKeyword('상품)')); $this->assertSame('"셔츠"', DatabaseFulltextEngine::sanitizeBooleanModeKeyword('(셔츠')); $this->assertSame('"키워드"', DatabaseFulltextEngine::sanitizeBooleanModeKeyword('~키워드')); $this->assertSame('"상품"', DatabaseFulltextEngine::sanitizeBooleanModeKeyword('상품*')); $this->assertSame('"셔츠"', DatabaseFulltextEngine::sanitizeBooleanModeKeyword('"셔츠')); $this->assertSame('"user"', DatabaseFulltextEngine::sanitizeBooleanModeKeyword('@user')); //