diff options
Diffstat (limited to 'src/__tests__')
| -rw-r--r-- | src/__tests__/VyOSElasticModernFulltextStorageEngineTestCase.php | 280 | ||||
| -rw-r--r-- | src/__tests__/VyOSElasticModernHostTestCase.php | 66 |
2 files changed, 346 insertions, 0 deletions
diff --git a/src/__tests__/VyOSElasticModernFulltextStorageEngineTestCase.php b/src/__tests__/VyOSElasticModernFulltextStorageEngineTestCase.php new file mode 100644 index 0000000..7e20fdc --- /dev/null +++ b/src/__tests__/VyOSElasticModernFulltextStorageEngineTestCase.php @@ -0,0 +1,280 @@ +<?php + +/** + * Test-double subclass of the concrete engine used by unit tests. + * Overrides host-dependent methods so tests run without a real + * PhabricatorSearchService; no actual Elasticsearch I/O occurs. + */ +final class VyOSElasticModernFulltextStorageEngineTestDouble + extends VyOSElasticModernFulltextStorageEngine { + + public function reindexAbstractDocument( + PhabricatorSearchAbstractDocument $doc) { + // no-op in tests + } + + public function executeSearch(PhabricatorSavedQuery $query) { + return array(); + } + + public function indexExists(?VyOSElasticModernHost $host = null) { + return false; + } + + public function getIndexStats(?VyOSElasticModernHost $host = null) { + return array(); + } + +} + +final class VyOSElasticModernFulltextStorageEngineTestCase + extends PhutilTestCase { + + private function newEngine() { + return new VyOSElasticModernFulltextStorageEngineTestDouble(); + } + + public function testSetVersionAcceptsSevenAndAbove() { + foreach (array(7, 8, 9, 100) as $v) { + $engine = $this->newEngine(); + $caught = null; + try { + $engine->setVersion($v); + } catch (Exception $e) { + $caught = $e; + } + $this->assertEqual( + null, + $caught, + pht('Expected no exception for version=%d.', $v)); + $this->assertEqual($v, $engine->getVersion()); + } + } + + public function testSetVersionRejectsBelowSeven() { + foreach (array(0, 1, 2, 5, 6) as $v) { + $engine = $this->newEngine(); + $caught = null; + try { + $engine->setVersion($v); + } catch (Exception $e) { + $caught = $e; + } + $this->assertTrue( + $caught !== null, + pht('Expected an exception for version=%d.', $v)); + } + } + + public function testGetDocumentUri() { + $engine = $this->newEngine()->setVersion(7); + $uri = $engine->getDocumentUri('TASK', 'PHID-TASK-abc'); + $this->assertEqual('/_doc/PHID-TASK-abc', $uri); + } + + public function testGetDocumentUriIgnoresType() { + // The typeless API does not encode the doc type in the URL. + $engine = $this->newEngine()->setVersion(7); + $uri_a = $engine->getDocumentUri('TASK', 'PHID-TASK-abc'); + $uri_b = $engine->getDocumentUri('DREV', 'PHID-TASK-abc'); + $this->assertEqual($uri_a, $uri_b); + } + + public function testGetSearchUri() { + $engine = $this->newEngine()->setVersion(7); + $this->assertEqual( + '/_search', + $engine->getSearchUri(array('TASK', 'DREV'))); + $this->assertEqual('/_search', $engine->getSearchUri(array('USER'))); + $this->assertEqual('/_search', $engine->getSearchUri(array())); + } + + public function testBuildTypeFilter() { + $engine = $this->newEngine()->setVersion(7); + $filter = $engine->buildTypeFilter(array('TASK', 'DREV')); + $expected = array( + 'terms' => array( + 'documentType' => array('TASK', 'DREV'), + ), + ); + $this->assertEqual($expected, $filter); + } + + public function testBuildTypeFilterEmpty() { + // Empty list still produces a filter; the caller is responsible + // for normalizing "no types specified" to "all indexable types" + // before calling this method. This matches the bundled engine's + // pattern (executeSearch() normalizes before URL construction). + $engine = $this->newEngine()->setVersion(7); + $filter = $engine->buildTypeFilter(array()); + $this->assertEqual( + array('terms' => array('documentType' => array())), + $filter); + } + + public function testBuildIndexMappingsShape() { + $engine = $this->newEngine()->setVersion(7); + + $doc_types = array('TASK', 'DREV'); + $fields = array('title', 'body', 'comment'); + $relationships = array('authorPHID', 'projectPHID'); + $mappings = $engine->buildIndexMappings( + $doc_types, $fields, $relationships, 'text'); + + // Single typeless mapping with one 'properties' block. + $this->assertTrue(isset($mappings['properties'])); + $this->assertFalse(isset($mappings['TASK'])); + $this->assertFalse(isset($mappings['DREV'])); + + // All three text fields have the multi-analyzer shape. + foreach (array('title', 'body', 'comment') as $field) { + $this->assertTrue( + isset($mappings['properties'][$field]), + pht('Field "%s" missing from mappings.', $field)); + $this->assertEqual( + 'text', + $mappings['properties'][$field]['type'], + pht('Field "%s" should be type text.', $field)); + $this->assertTrue( + isset($mappings['properties'][$field]['fields']['raw']), + pht('Field "%s" missing raw sub-field.', $field)); + $this->assertTrue( + isset($mappings['properties'][$field]['fields']['keywords']), + pht('Field "%s" missing keywords sub-field.', $field)); + $this->assertTrue( + isset($mappings['properties'][$field]['fields']['stems']), + pht('Field "%s" missing stems sub-field.', $field)); + } + + // Both relationships emit as keyword fields with doc_values:false. + foreach (array('authorPHID', 'projectPHID') as $rel) { + $this->assertEqual( + 'keyword', + $mappings['properties'][$rel]['type'], + pht('Relationship "%s" should be keyword type.', $rel)); + $this->assertEqual( + false, + $mappings['properties'][$rel]['doc_values'], + pht('Relationship "%s" should have doc_values:false.', $rel)); + $this->assertEqual( + 'date', + $mappings['properties'][$rel.'_ts']['type'], + pht('Relationship "%s" missing timestamp field.', $rel)); + // No include_in_all anywhere. + $this->assertFalse( + isset($mappings['properties'][$rel]['include_in_all']), + pht('Relationship "%s" should not have include_in_all.', $rel)); + } + + // documentType is a keyword field inside properties. + $this->assertEqual( + 'keyword', + $mappings['properties']['documentType']['type']); + + // Standard date fields present. + $this->assertEqual( + 'date', + $mappings['properties']['dateCreated']['type']); + $this->assertEqual( + 'date', + $mappings['properties']['lastModified']['type']); + } + + public function testBuildIndexMappingsRejectsReservedFieldName() { + $engine = $this->newEngine()->setVersion(7); + $caught = null; + try { + $engine->buildIndexMappings( + array(), array('documentType'), array(), 'text'); + } catch (Exception $e) { + $caught = $e; + } + $this->assertTrue( + $caught !== null, + pht('Expected exception when field name collides with reserved key.')); + } + + public function testBuildIndexMappingsRejectsReservedRelationshipName() { + $engine = $this->newEngine()->setVersion(7); + $caught = null; + try { + $engine->buildIndexMappings( + array(), array(), array('lastModified'), 'text'); + } catch (Exception $e) { + $caught = $e; + } + $this->assertTrue( + $caught !== null, + pht('Expected exception when relationship name collides with reserved key.')); + } + + public function testBuildIndexMappingsRejectsFooTsClash() { + // A field named "foo_ts" would clash with the timestamp slot auto-generated + // for a relationship named "foo". + $engine = $this->newEngine()->setVersion(7); + $caught = null; + try { + $engine->buildIndexMappings( + array(), array('foo_ts'), array('foo'), 'text'); + } catch (Exception $e) { + $caught = $e; + } + $this->assertTrue( + $caught !== null, + pht('Expected exception for field/relationship timestamp-slot collision.')); + } + + public function testBuildIndexMappingsEmptyInputsYieldStandardFields() { + $engine = $this->newEngine()->setVersion(7); + $mappings = $engine->buildIndexMappings(array(), array(), array(), 'text'); + $this->assertTrue(isset($mappings['properties']['documentType'])); + $this->assertTrue(isset($mappings['properties']['dateCreated'])); + $this->assertTrue(isset($mappings['properties']['lastModified'])); + $this->assertEqual( + 'keyword', $mappings['properties']['documentType']['type']); + $this->assertEqual('date', $mappings['properties']['dateCreated']['type']); + $this->assertEqual('date', $mappings['properties']['lastModified']['type']); + } + + public function testEngineIdentifier() { + $engine = $this->newEngine(); + $this->assertEqual('elasticsearch-modern', $engine->getEngineIdentifier()); + } + + public function testHostType() { + $engine = $this->newEngine(); + $host = $engine->getHostType(); + $this->assertTrue($host instanceof VyOSElasticModernHost); + } + + public function testGetTextFieldType() { + $engine = $this->newEngine()->setVersion(7); + $this->assertEqual('text', $engine->getTextFieldType()); + } + + public function testGetTimestampField() { + $engine = $this->newEngine()->setVersion(7); + $this->assertEqual('lastModified', $engine->getTimestampField()); + } + + public function testBuildSpecTreatsZeroQueryAsSearchTerm() { + // '0' is falsy in PHP but must be treated as a non-empty search term. + // strlen('0') == 1, so the query clause should be present and the + // default date-sorted path should NOT fire. + $engine = $this->newEngine()->setVersion(7); + $query = id(new PhabricatorSavedQuery()) + ->setParameter('query', '0'); + + $method = new ReflectionMethod($engine, 'buildSpec'); + $method->setAccessible(true); + $spec = $method->invoke($engine, $query, array('TASK')); + + $this->assertFalse(isset($spec['sort'])); + $this->assertEqual( + '0', + idxv( + $spec, + array('query', 'bool', 'must', 0, 'simple_query_string', 'query'))); + } + +} diff --git a/src/__tests__/VyOSElasticModernHostTestCase.php b/src/__tests__/VyOSElasticModernHostTestCase.php new file mode 100644 index 0000000..8fcd9b6 --- /dev/null +++ b/src/__tests__/VyOSElasticModernHostTestCase.php @@ -0,0 +1,66 @@ +<?php + +final class VyOSElasticModernHostTestEngine + extends VyOSElasticModernFulltextStorageEngine { +} + +final class VyOSElasticModernHostTestCase + extends PhutilTestCase { + + public function testEngineIsConcreteForDispatch() { + // Must NOT be abstract: PhutilClassMapQuery → PhutilSymbolLoader::loadObjects() + // calls setConcreteOnly(true) which unsets abstract classes. A concrete (plain) + // class is required for cluster.search[].type:elasticsearch-modern dispatch. + $class = new ReflectionClass('VyOSElasticModernFulltextStorageEngine'); + $this->assertFalse($class->isAbstract()); + } + + public function testDisplayName() { + $engine = new VyOSElasticModernHostTestEngine(); + $host = new VyOSElasticModernHost($engine); + $this->assertEqual( + 'Elasticsearch (modern)', + (string)$host->getDisplayName()); + } + + public function testConfigDefaults() { + $engine = new VyOSElasticModernHostTestEngine(); + $host = new VyOSElasticModernHost($engine); + $host->setConfig(array()); + $this->assertEqual('http', $host->getProtocol()); + $this->assertEqual('phabricator/', $host->getPath()); + $this->assertEqual(7, $host->getVersion()); + } + + public function testConfigOverrides() { + $engine = new VyOSElasticModernHostTestEngine(); + $host = new VyOSElasticModernHost($engine); + $host->setConfig(array( + 'host' => 'es.example.com', + 'port' => 9200, + 'protocol' => 'https', + 'path' => '/myphorge', + 'version' => 8, + )); + $this->assertEqual('es.example.com', $host->getHost()); + $this->assertEqual(9200, $host->getPort()); + $this->assertEqual('https', $host->getProtocol()); + $this->assertEqual('/myphorge', $host->getPath()); + $this->assertEqual(8, $host->getVersion()); + } + + public function testGetURI() { + $engine = new VyOSElasticModernHostTestEngine(); + $host = new VyOSElasticModernHost($engine); + $host->setConfig(array( + 'host' => 'es.example.com', + 'port' => 9200, + 'protocol' => 'http', + 'path' => '/phabricator', + )); + $uri = (string)$host->getURI('/_doc/abc'); + $this->assertTrue(strpos($uri, 'es.example.com') !== false); + $this->assertTrue(strpos($uri, '/_doc/abc') !== false); + } + +} |
