API Update Subsite integration, remove Polyhome variant

Add a method to clear cached variants from SearchVariant, and a configuration flag for whether
a variant should be enabled or not. Add a FullTextSearch TestState class which will globally
disable the queuedjobs and fulltextsearch shutdown handlers during tests, and is not used to
clear cached variants on each test to prevent global state leakage.

Also removes Phockito as a test dependency.
This commit is contained in:
Robbie Averill 2017-12-05 09:11:51 +13:00
parent ceff657c62
commit 861f87514d
26 changed files with 377 additions and 524 deletions

View File

@ -12,6 +12,8 @@ matrix:
env: DB=MYSQL PHPUNIT_TEST=1 env: DB=MYSQL PHPUNIT_TEST=1
- php: 7.1 - php: 7.1
env: DB=MYSQL PHPUNIT_COVERAGE_TEST=1 env: DB=MYSQL PHPUNIT_COVERAGE_TEST=1
- php: 7.1
env: DB=MYSQL PHPUNIT_TEST=1 SUBSITES=1
- php: 7.2 - php: 7.2
env: DB=MYSQL PHPUNIT_TEST=1 env: DB=MYSQL PHPUNIT_TEST=1
@ -21,7 +23,7 @@ before_script:
- composer validate - composer validate
- composer require --no-update symbiote/silverstripe-queuedjobs ^4.0 - composer require --no-update symbiote/silverstripe-queuedjobs ^4.0
# todo: Add Subsites in - if [[ $SUBSITES ]]; then composer require --no-update silverstripe/subsites 2.0.x-dev; fi
- composer require --no-update silverstripe/installer 4.0.x-dev - composer require --no-update silverstripe/installer 4.0.x-dev
- composer install --prefer-dist --no-interaction --no-progress --no-suggest --optimize-autoloader --verbose --profile - composer install --prefer-dist --no-interaction --no-progress --no-suggest --optimize-autoloader --verbose --profile

View File

@ -16,7 +16,6 @@ mappings:
SearchUpdater_ObjectHandler: SilverStripe\FullTextSearch\Search\Updaters\SearchUpdater_ObjectHandler SearchUpdater_ObjectHandler: SilverStripe\FullTextSearch\Search\Updaters\SearchUpdater_ObjectHandler
SearchVariant: SilverStripe\FullTextSearch\Search\Variants\SearchVariant SearchVariant: SilverStripe\FullTextSearch\Search\Variants\SearchVariant
SearchVariant_Caller: SilverStripe\FullTextSearch\Search\Variants\SearchVariant_Caller SearchVariant_Caller: SilverStripe\FullTextSearch\Search\Variants\SearchVariant_Caller
SearchVariantSiteTreeSubsitesPolyhome: SilverStripe\FullTextSearch\Search\Variants\SearchVariantSiteTreeSubsitesPolyhome
SearchVariantSubsites: SilverStripe\FullTextSearch\Search\Variants\SearchVariantSubsites SearchVariantSubsites: SilverStripe\FullTextSearch\Search\Variants\SearchVariantSubsites
SearchVariantVersioned: SilverStripe\FullTextSearch\Search\Variants\SearchVariantVersioned SearchVariantVersioned: SilverStripe\FullTextSearch\Search\Variants\SearchVariantVersioned
SolrReindexBase: SilverStripe\FullTextSearch\Solr\Reindex\Handlers\SolrReindexBase SolrReindexBase: SilverStripe\FullTextSearch\Solr\Reindex\Handlers\SolrReindexBase

View File

@ -13,7 +13,6 @@ Adds support for fulltext search engines like Sphinx and Solr to SilverStripe CM
## Requirements ## Requirements
* SilverStripe 4.0+ * SilverStripe 4.0+
* (optional) [silverstripe-phockito](https://github.com/hafriedlander/silverstripe-phockito) (for testing)
**Note:** For SilverStripe 3.x, please use the [2.x release line](https://github.com/silverstripe/silverstripe-fulltextsearch/tree/2). **Note:** For SilverStripe 3.x, please use the [2.x release line](https://github.com/silverstripe/silverstripe-fulltextsearch/tree/2).

8
_config/tests.yml Normal file
View File

@ -0,0 +1,8 @@
---
Name: fulltextsearchtests
---
SilverStripe\Core\Injector\Injector:
SilverStripe\Dev\State\SapphireTestState:
properties:
States:
fulltextsearch: '%$SilverStripe\FullTextSearch\Tests\State\FullTextSearchState'

View File

@ -1,4 +1,5 @@
<?php <?php
namespace SilverStripe\FullTextSearch\Search; namespace SilverStripe\FullTextSearch\Search;
use SilverStripe\Core\ClassInfo; use SilverStripe\Core\ClassInfo;

View File

@ -4,6 +4,7 @@ namespace SilverStripe\FullTextSearch\Search\Variants;
use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataObject;
use SilverStripe\Core\ClassInfo; use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Config\Configurable;
use SilverStripe\FullTextSearch\Utils\CombinationsArrayIterator; use SilverStripe\FullTextSearch\Utils\CombinationsArrayIterator;
use ReflectionClass; use ReflectionClass;
@ -13,6 +14,16 @@ use ReflectionClass;
*/ */
abstract class SearchVariant abstract class SearchVariant
{ {
use Configurable;
/**
* Whether this variant is enabled
*
* @config
* @var boolean
*/
private static $enabled = true;
public function __construct() public function __construct()
{ {
} }
@ -30,7 +41,7 @@ abstract class SearchVariant
*/ */
public function appliesToEnvironment() public function appliesToEnvironment()
{ {
return true; return $this->config()->get('enabled');
} }
/** /**
@ -117,6 +128,14 @@ abstract class SearchVariant
} }
} }
/**
* Clear the cached variants
*/
public static function clear_variant_cache()
{
self::$class_variants = [];
}
/** Holds a cache of SearchVariant_Caller instances, one for each class/includeSubclasses setting */ /** Holds a cache of SearchVariant_Caller instances, one for each class/includeSubclasses setting */
protected static $call_instances = array(); protected static $call_instances = array();
@ -175,14 +194,14 @@ abstract class SearchVariant
/** /**
* Activate all the states in the passed argument * Activate all the states in the passed argument
* @static * @static
* @param (array) $state. A set of (string)$variantClass => (any)$state pairs , e.g. as returned by * @param array $state A set of (string)$variantClass => (any)$state pairs , e.g. as returned by
* SearchVariant::current_state() * SearchVariant::current_state()
* @return void * @return void
*/ */
public static function activate_state($state) public static function activate_state($state)
{ {
foreach (self::variants() as $variant => $instance) { foreach (self::variants() as $variant => $instance) {
if (isset($state[$variant])) { if (isset($state[$variant]) && $instance->appliesToEnvironment()) {
$instance->activateState($state[$variant]); $instance->activateState($state[$variant]);
} }
} }

View File

@ -1,103 +0,0 @@
<?php
namespace SilverStripe\FullTextSearch\Search\Variants;
use SilverStripe\ORM\Queries\SQLSelect;
use SilverStripe\FullTextSearch\Search\SearchIntrospection;
if (!class_exists('Subsite') || !class_exists('SubsitePolyhome')) {
return;
}
class SearchVariantSiteTreeSubsitesPolyhome extends SearchVariant
{
public function appliesToEnvironment()
{
return class_exists('Subsite') && class_exists('SubsitePolyhome');
}
public function appliesTo($class, $includeSubclasses)
{
return SearchIntrospection::has_extension($class, 'SiteTreeSubsitesPolyhome', $includeSubclasses);
}
public function currentState()
{
return Subsite::currentSubsiteID();
}
public function reindexStates()
{
static $ids = null;
if ($ids === null) {
$ids = array(0);
foreach (DataObject::get('Subsite') as $subsite) {
$ids[] = $subsite->ID;
}
}
return $ids;
}
public function activateState($state)
{
if (Controller::has_curr()) {
Subsite::changeSubsite($state);
} else {
// TODO: This is a nasty hack - calling Subsite::changeSubsite after request ends
// throws error because no current controller to access session on
$_REQUEST['SubsiteID'] = $state;
}
}
public function alterDefinition($class, $index)
{
$self = get_class($this);
$this->addFilterField($index, '_subsite', array(
'name' => '_subsite',
'field' => '_subsite',
'fullfield' => '_subsite',
'base' => DataObject::getSchema()->baseDataClass($class),
'origin' => $class,
'type' => 'Int',
'lookup_chain' => array(array('call' => 'variant', 'variant' => $self, 'method' => 'currentState'))
));
}
public function alterQuery($query, $index)
{
$subsite = Subsite::currentSubsiteID();
$query->filter('_subsite', array($subsite, SearchQuery::$missing));
}
public static $subsites = null;
/**
* We need _really_ complicated logic to find just the changed subsites (because we use versions there's no explicit
* deletes, just new versions with different members) so just always use all of them
*/
public function extractManipulationWriteState(&$writes)
{
$self = get_class($this);
foreach ($writes as $key => $write) {
if (!$this->appliesTo($write['class'], true)) {
continue;
}
if (self::$subsites === null) {
$query = new SQLSelect('ID', 'Subsite');
self::$subsites = array_merge(array('0'), $query->execute()->column());
}
$next = array();
foreach ($write['statefulids'] as $i => $statefulid) {
foreach (self::$subsites as $subsiteID) {
$next[] = array('id' => $statefulid['id'], 'state' => array_merge($statefulid['state'], array($self => $subsiteID)));
}
}
$writes[$key]['statefulids'] = $next;
}
}
}

View File

@ -2,13 +2,21 @@
namespace SilverStripe\FullTextSearch\Search\Variants; namespace SilverStripe\FullTextSearch\Search\Variants;
use SilverStripe\Assets\File;
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\FullTextSearch\Search\SearchIntrospection;
use SilverStripe\FullTextSearch\Search\Queries\SearchQuery;
use SilverStripe\ORM\Queries\SQLSelect; use SilverStripe\ORM\Queries\SQLSelect;
use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataObject;
use SilverStripe\Security\Permission; use SilverStripe\Security\Permission;
use SilverStripe\FullTextSearch\Search\SearchIntrospection; use SilverStripe\Subsites\Model\Subsite;
use SilverStripe\FullTextSearch\Search\Queries\SearchQuery; use SilverStripe\Subsites\Extensions\SiteTreeSubsites;
use SilverStripe\Subsites\Extensions\GroupSubsites;
use SilverStripe\Subsites\Extensions\FileSubsites;
use SilverStripe\Subsites\Extensions\SiteConfigSubsites;
use SilverStripe\Subsites\State\SubsiteState;
if (!class_exists('Subsite')) { if (!class_exists(Subsite::class)) {
return; return;
} }
@ -16,22 +24,26 @@ class SearchVariantSubsites extends SearchVariant
{ {
public function appliesToEnvironment() public function appliesToEnvironment()
{ {
return class_exists('Subsite'); return class_exists(Subsite::class) && parent::appliesToEnvironment();
} }
public function appliesTo($class, $includeSubclasses) public function appliesTo($class, $includeSubclasses)
{ {
if (!$this->appliesToEnvironment()) {
return false;
}
// Include all DataExtensions that contain a SubsiteID. // Include all DataExtensions that contain a SubsiteID.
// TODO: refactor subsites to inherit a common interface, so we can run introspection once only. // TODO: refactor subsites to inherit a common interface, so we can run introspection once only.
return SearchIntrospection::has_extension($class, 'SiteTreeSubsites', $includeSubclasses) || return SearchIntrospection::has_extension($class, SiteTreeSubsites::class, $includeSubclasses)
SearchIntrospection::has_extension($class, 'GroupSubsites', $includeSubclasses) || || SearchIntrospection::has_extension($class, GroupSubsites::class, $includeSubclasses)
SearchIntrospection::has_extension($class, 'FileSubsites', $includeSubclasses) || || SearchIntrospection::has_extension($class, FileSubsites::class, $includeSubclasses)
SearchIntrospection::has_extension($class, 'SiteConfigSubsites', $includeSubclasses); || SearchIntrospection::has_extension($class, SiteConfigSubsites::class, $includeSubclasses);
} }
public function currentState() public function currentState()
{ {
return (string)Subsite::currentSubsiteID(); return (string) SubsiteState::singleton()->getSubsiteId();
} }
public function reindexStates() public function reindexStates()
@ -39,9 +51,9 @@ class SearchVariantSubsites extends SearchVariant
static $ids = null; static $ids = null;
if ($ids === null) { if ($ids === null) {
$ids = array('0'); $ids = ['0'];
foreach (DataObject::get('Subsite') as $subsite) { foreach (Subsite::get() as $subsite) {
$ids[] = (string)$subsite->ID; $ids[] = (string) $subsite->ID;
} }
} }
@ -50,26 +62,34 @@ class SearchVariantSubsites extends SearchVariant
public function activateState($state) public function activateState($state)
{ {
// We always just set the $_GET variable rather than store in Session - this always works, has highest priority if (!$this->appliesToEnvironment()) {
// in Subsite::currentSubsiteID() and doesn't persist unlike Subsite::changeSubsite return;
$_GET['SubsiteID'] = $state; }
Permission::flush_permission_cache();
// Note: Setting directly to the SubsiteState because we don't want the subsite ID to be persisted
// like Subsite::changeSubsite would do.
SubsiteState::singleton()->setSubsiteId($state);
Permission::reset();
} }
public function alterDefinition($class, $index) public function alterDefinition($class, $index)
{ {
$self = get_class($this); $self = get_class($this);
if (!$this->appliesTo($class, true)) {
return;
}
// Add field to root // Add field to root
$this->addFilterField($index, '_subsite', array( $this->addFilterField($index, '_subsite', [
'name' => '_subsite', 'name' => '_subsite',
'field' => '_subsite', 'field' => '_subsite',
'fullfield' => '_subsite', 'fullfield' => '_subsite',
'base' => DataObject::getSchema()->baseDataClass($class), 'base' => DataObject::getSchema()->baseDataClass($class),
'origin' => $class, 'origin' => $class,
'type' => 'Int', 'type' => 'Int',
'lookup_chain' => array(array('call' => 'variant', 'variant' => $self, 'method' => 'currentState')) 'lookup_chain' => [['call' => 'variant', 'variant' => $self, 'method' => 'currentState']],
)); ]);
} }
/** /**
@ -83,12 +103,12 @@ class SearchVariantSubsites extends SearchVariant
*/ */
public function alterQuery($query, $index) public function alterQuery($query, $index)
{ {
if ($this->isFieldFiltered('_subsite', $query)) { if ($this->isFieldFiltered('_subsite', $query) || !$this->appliesToEnvironment()) {
return; return;
} }
$subsite = Subsite::currentSubsiteID(); $subsite = $this->currentState();
$query->filter('_subsite', array($subsite, SearchQuery::$missing)); $query->filter('_subsite', [$subsite, SearchQuery::$missing]);
} }
/** /**
@ -98,8 +118,9 @@ class SearchVariantSubsites extends SearchVariant
public function extractManipulationWriteState(&$writes) public function extractManipulationWriteState(&$writes)
{ {
$self = get_class($this); $self = get_class($this);
$query = new SQLSelect('"ID"', '"Subsite"'); $tableName = DataObject::getSchema()->tableName(Subsite::class);
$subsites = array_merge(array('0'), $query->execute()->column()); $query = SQLSelect::create('"ID"', '"' . $tableName . '"');
$subsites = array_merge(['0'], $query->execute()->column());
foreach ($writes as $key => $write) { foreach ($writes as $key => $write) {
$applies = $this->appliesTo($write['class'], true); $applies = $this->appliesTo($write['class'], true);
@ -107,25 +128,27 @@ class SearchVariantSubsites extends SearchVariant
continue; continue;
} }
if (isset($write['fields']['SiteTree:SubsiteID'])) { if (isset($write['fields'][SiteTree::class . ':SubsiteID'])) {
$subsitesForWrite = array($write['fields']['SiteTree:SubsiteID']); $subsitesForWrite = [$write['fields'][SiteTree::class . ':SubsiteID']];
} // files in subsite 0 should be in all subsites as they are global } elseif (isset($write['fields'][File::class . ':SubsiteID'])
elseif (isset($write['fields']['File:SubsiteID']) && intval($write['fields']['File:SubsiteID']) !== 0) { && (int) $write['fields'][File::class . ':SubsiteID'] !== 0
$subsitesForWrite = array($write['fields']['File:SubsiteID']); ) {
// files in subsite 0 should be in all subsites as they are global
$subsitesForWrite = [$write['fields'][File::class . ':SubsiteID']];
} else { } else {
$subsitesForWrite = $subsites; $subsitesForWrite = $subsites;
} }
$next = array(); $next = [];
foreach ($write['statefulids'] as $i => $statefulid) { foreach ($write['statefulids'] as $i => $statefulid) {
foreach ($subsitesForWrite as $subsiteID) { foreach ($subsitesForWrite as $subsiteID) {
$next[] = array( $next[] = [
'id' => $statefulid['id'], 'id' => $statefulid['id'],
'state' => array_merge( 'state' => array_merge(
$statefulid['state'], $statefulid['state'],
array($self => (string)$subsiteID) [$self => (string) $subsiteID]
) ),
); ];
} }
} }
$writes[$key]['statefulids'] = $next; $writes[$key]['statefulids'] = $next;

View File

@ -12,6 +12,10 @@ class SearchVariantVersioned extends SearchVariant
{ {
public function appliesTo($class, $includeSubclasses) public function appliesTo($class, $includeSubclasses)
{ {
if (!$this->appliesToEnvironment()) {
return false;
}
return SearchIntrospection::has_extension($class, Versioned::class, $includeSubclasses); return SearchIntrospection::has_extension($class, Versioned::class, $includeSubclasses);
} }
@ -57,8 +61,6 @@ class SearchVariantVersioned extends SearchVariant
public function extractManipulationState(&$manipulation) public function extractManipulationState(&$manipulation)
{ {
$self = get_class($this);
foreach ($manipulation as $table => $details) { foreach ($manipulation as $table => $details) {
$class = $details['class']; $class = $details['class'];
$stage = Versioned::DRAFT; $stage = Versioned::DRAFT;
@ -70,7 +72,7 @@ class SearchVariantVersioned extends SearchVariant
if (ClassInfo::exists($class) && $this->appliesTo($class, false)) { if (ClassInfo::exists($class) && $this->appliesTo($class, false)) {
$manipulation[$table]['class'] = $class; $manipulation[$table]['class'] = $class;
$manipulation[$table]['state'][$self] = $stage; $manipulation[$table]['state'][get_class($this)] = $stage;
} }
} }
} }
@ -83,10 +85,9 @@ class SearchVariantVersioned extends SearchVariant
if (ClassInfo::exists($class) && $this->appliesTo($class, false)) { if (ClassInfo::exists($class) && $this->appliesTo($class, false)) {
$table = $class; $table = $class;
$self = get_class($this);
foreach ($ids as $i => $statefulid) { foreach ($ids as $i => $statefulid) {
$ids[$i]['state'][$self] = $suffix ? $suffix : Versioned::DRAFT; $ids[$i]['state'][get_class($this)] = $suffix ?: Versioned::DRAFT;
} }
} }
} }

View File

@ -1,4 +1,5 @@
<?php <?php
namespace SilverStripe\FullTextSearch\Solr\Services; namespace SilverStripe\FullTextSearch\Solr\Services;
class Solr4Service extends SolrService class Solr4Service extends SolrService

View File

@ -2,12 +2,11 @@
namespace SilverStripe\FullTextSearch\Tests; namespace SilverStripe\FullTextSearch\Tests;
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Dev\SapphireTest; use SilverStripe\Dev\SapphireTest;
use SilverStripe\FullTextSearch\Search\FullTextSearch; use SilverStripe\FullTextSearch\Search\FullTextSearch;
use SilverStripe\ORM\FieldType\DBDatetime;
use SilverStripe\Core\Config\Config;
use SilverStripe\Versioned\Versioned;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\FullTextSearch\Tests\BatchedProcessorTest\BatchedProcessor_QueuedJobService; use SilverStripe\FullTextSearch\Tests\BatchedProcessorTest\BatchedProcessor_QueuedJobService;
use SilverStripe\FullTextSearch\Tests\BatchedProcessorTest\BatchedProcessorTest_Index; use SilverStripe\FullTextSearch\Tests\BatchedProcessorTest\BatchedProcessorTest_Index;
use SilverStripe\FullTextSearch\Tests\BatchedProcessorTest\BatchedProcessorTest_Object; use SilverStripe\FullTextSearch\Tests\BatchedProcessorTest\BatchedProcessorTest_Object;
@ -16,6 +15,9 @@ use SilverStripe\FullTextSearch\Search\Processors\SearchUpdateQueuedJobProcessor
use SilverStripe\FullTextSearch\Search\Processors\SearchUpdateBatchedProcessor; use SilverStripe\FullTextSearch\Search\Processors\SearchUpdateBatchedProcessor;
use SilverStripe\FullTextSearch\Search\Updaters\SearchUpdater; use SilverStripe\FullTextSearch\Search\Updaters\SearchUpdater;
use SilverStripe\FullTextSearch\Search\Variants\SearchVariantVersioned; use SilverStripe\FullTextSearch\Search\Variants\SearchVariantVersioned;
use SilverStripe\ORM\FieldType\DBDatetime;
use SilverStripe\Versioned\Versioned;
use Symbiote\QueuedJobs\Services\QueuedJob;
use Symbiote\QueuedJobs\Services\QueuedJobService; use Symbiote\QueuedJobs\Services\QueuedJobService;
/** /**
@ -29,37 +31,30 @@ class BatchedProcessorTest extends SapphireTest
BatchedProcessorTest_Object::class BatchedProcessorTest_Object::class
); );
protected $illegalExtensions = array( protected static $illegal_extensions = [
'SiteTree' => array( SiteTree::class => [
'SiteTreeSubsites', SiteTreeSubsites::class,
'Translatable' ],
) ];
);
public function setUpOnce() public static function setUpBeforeClass()
{ {
// Disable illegal extensions if skipping this test // Disable illegal extensions if skipping this test
if (class_exists('Subsite') || !interface_exists('Symbiote\QueuedJobs\Services\QueuedJob')) { if (class_exists(Subsite::class) || !interface_exists(QueuedJob::class)) {
$this->illegalExtensions = array(); static::$illegal_extensions = [];
} }
parent::setUpOnce(); parent::setUpBeforeClass();
} }
protected function setUp() protected function setUp()
{ {
Config::modify()->set(SearchUpdater::class, 'flush_on_shutdown', false);
parent::setUp(); parent::setUp();
if (!interface_exists('Symbiote\QueuedJobs\Services\QueuedJob')) { if (!interface_exists(QueuedJob::class)) {
$this->skipTest = true;
$this->markTestSkipped("These tests need the QueuedJobs module installed to run"); $this->markTestSkipped("These tests need the QueuedJobs module installed to run");
} }
Config::modify()->set(QueuedJobService::class, 'use_shutdown_function', false); if (class_exists(Subsite::class)) {
if (class_exists('Subsite')) {
$this->skipTest = true;
$this->markTestSkipped(get_class() . ' skipped when running with subsites'); $this->markTestSkipped(get_class() . ' skipped when running with subsites');
} }
@ -69,7 +64,7 @@ class BatchedProcessorTest extends SapphireTest
Config::modify()->set(SearchUpdateBatchedProcessor::class, 'batch_soft_cap', 0); Config::modify()->set(SearchUpdateBatchedProcessor::class, 'batch_soft_cap', 0);
Config::modify()->set(SearchUpdateCommitJobProcessor::class, 'cooldown', 600); Config::modify()->set(SearchUpdateCommitJobProcessor::class, 'cooldown', 600);
Versioned::set_stage("Stage"); Versioned::set_stage(Versioned::DRAFT);
Injector::inst()->registerService(new BatchedProcessor_QueuedJobService(), QueuedJobService::class); Injector::inst()->registerService(new BatchedProcessor_QueuedJobService(), QueuedJobService::class);

View File

@ -22,8 +22,6 @@ class SearchUpdaterTest extends SapphireTest
protected function setUp() protected function setUp()
{ {
Config::modify()->set(SearchUpdater::class, 'flush_on_shutdown', false);
parent::setUp(); parent::setUp();
if (self::$index === null) { if (self::$index === null) {

View File

@ -1,74 +0,0 @@
<?php
namespace SilverStripe\FullTextSearch\Tests;
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\FullTextSearch\Search\Indexes\SearchIndex_Recording;
use SilverStripe\FullTextSearch\Tests\SearchVariantSiteTreeSubsitesPolyhomeTest\SearchVariantSiteTreeSubsitesPolyhomeTest_Index;
use SilverStripe\FullTextSearch\Tests\SearchVariantSiteTreeSubsitesPolyhomeTest\SearchVariantSiteTreeSubsitesPolyhomeTest_Item;
class SearchVariantSiteTreeSubsitesPolyhomeTest extends SapphireTest
{
private static $index = null;
private static $subsite_a = null;
private static $subsite_b = null;
public function setUp()
{
parent::setUp();
// Check subsites installed
if (!class_exists('Subsite') || !class_exists('SubsitePolyhome')) {
return $this->markTestSkipped('The subsites polyhome module is not installed');
}
if (self::$index === null) {
self::$index = singleton('SearchVariantSiteTreeSubsitesPolyhomeTest_Index');
}
if (self::$subsite_a === null) {
self::$subsite_a = new Subsite();
self::$subsite_a->write();
self::$subsite_b = new Subsite();
self::$subsite_b->write();
}
FullTextSearch::force_index_list(self::$index);
SearchUpdater::clear_dirty_indexes();
}
public function testSavingDirect()
{
// Initial add
$item = new SearchVariantSiteTreeSubsitesPolyhomeTest_Item();
$item->write();
SearchUpdater::flush_dirty_indexes();
$this->assertEquals(self::$index->getAdded(array('ID', '_subsite')), array(
array('ID' => $item->ID, '_subsite' => 0)
));
// Check that adding to subsites works
self::$index->reset();
$item->setField('AddToSubsite[0]', 1);
$item->setField('AddToSubsite['.(self::$subsite_a->ID).']', 1);
$item->write();
SearchUpdater::flush_dirty_indexes();
$this->assertEquals(self::$index->getAdded(array('ID', '_subsite')), array(
array('ID' => $item->ID, '_subsite' => 0),
array('ID' => $item->ID, '_subsite' => self::$subsite_a->ID)
));
$this->assertEquals(self::$index->deleted, array(
array('base' => 'SiteTree', 'id' => $item->ID, 'state' => array(
'SearchVariantVersioned' => 'Stage', 'SearchVariantSiteTreeSubsitesPolyhome' => self::$subsite_b->ID
))
));
}
}

View File

@ -1,14 +0,0 @@
<?php
namespace SilverStripe\FullTextSearch\Tests\SearchVariantSiteTreeSubsitesPolyhomeTest;
use SilverStripe\FullTextSearch\Search\Indexes\SearchIndex_Recording;
class SearchVariantSiteTreeSubsitesPolyhomeTest_Index extends SearchIndex_Recording
{
public function init()
{
$this->addClass(SearchVariantSiteTreeSubsitesPolyhomeTest_Item::class);
$this->addFilterField('TestText');
}
}

View File

@ -1,15 +0,0 @@
<?php
namespace SilverStripe\FullTextSearch\Tests\SearchVariantSiteTreeSubsitesPolyhomeTest;
use SilverStripe\CMS\Model\SiteTree;
class SearchVariantSiteTreeSubsitesPolyhomeTest_Item extends SiteTree
{
private static $table_name = 'SearchVariantSiteTreeSubsitesPolyhomeTest_Item';
// TODO: Currently theres a failure if you addClass a non-table class
private static $db = array(
'TestText' => 'Varchar'
);
}

View File

@ -2,32 +2,40 @@
namespace SilverStripe\FullTextSearch\Tests; namespace SilverStripe\FullTextSearch\Tests;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Dev\SapphireTest; use SilverStripe\Dev\SapphireTest;
use SilverStripe\FullTextSearch\Search\FullTextSearch;
use SilverStripe\FullTextSearch\Search\Processors\SearchUpdateProcessor;
use SilverStripe\FullTextSearch\Search\Queries\SearchQuery;
use SilverStripe\FullTextSearch\Search\Updaters\SearchUpdater;
use SilverStripe\FullTextSearch\Search\Variants\SearchVariantSubsites;
use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_Container;
use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_FakeIndex;
use SilverStripe\Subsites\Model\Subsite;
class SearchVariantSubsiteTest extends SapphireTest class SearchVariantSubsiteTest extends SapphireTest
{ {
private static $index = null; private static $index = null;
protected function setUp()
public function setUp()
{ {
parent::setUp(); parent::setUp();
// Check versioned available // Check versioned available
if (!class_exists('Subsite')) { if (!class_exists(Subsite::class)) {
return $this->markTestSkipped('The subsites module is not installed'); return $this->markTestSkipped('The subsites module is not installed');
} }
if (self::$index === null) { if (self::$index === null) {
self::$index = singleton('SearchVariantSubsiteTest'); self::$index = singleton(static::class);
} }
SearchUpdater::bind_manipulation_capture(); SearchUpdater::bind_manipulation_capture();
Config::inst()->update('Injector', 'SearchUpdateProcessor', array( Config::modify()->set(Injector::class, SearchUpdateProcessor::class, [
'class' => 'SearchUpdateImmediateProcessor' 'class' => SearchUpdateImmediateProcessor::class
)); ]);
FullTextSearch::force_index_list(self::$index); FullTextSearch::force_index_list(self::$index);
SearchUpdater::clear_dirty_indexes(); SearchUpdater::clear_dirty_indexes();
@ -41,13 +49,13 @@ class SearchVariantSubsiteTest extends SapphireTest
//typical behaviour: nobody is explicitly filtering on subsite, so the search variant adds a filter to the query //typical behaviour: nobody is explicitly filtering on subsite, so the search variant adds a filter to the query
$this->assertArrayNotHasKey('_subsite', $query->require); $this->assertArrayNotHasKey('_subsite', $query->require);
$variant = new SearchVariantSubsites(); $variant = new SearchVariantSubsites();
$variant->alterDefinition('SearchUpdaterTest_Container', $index); $variant->alterDefinition(SearchUpdaterTest_Container::class, $index);
$variant->alterQuery($query, $index); $variant->alterQuery($query, $index);
//check that the "default" query has been put in place: it's not empty, and we're searching on Subsite ID:0 and //check that the "default" query has been put in place: it's not empty, and we're searching on Subsite ID:0 and
// an object of SearchQuery::missing // an object of SearchQuery::missing
$this->assertNotEmpty($query->require['_subsite']); $this->assertNotEmpty($query->require['_subsite']);
$this->assertEquals(0, $query->require['_subsite'][0]); $this->assertEmpty($query->require['_subsite'][0]);
//check that SearchQuery::missing is set (by default, it is an object of stdClass) //check that SearchQuery::missing is set (by default, it is an object of stdClass)
$this->assertInstanceOf('stdClass', $query->require['_subsite'][1]); $this->assertInstanceOf('stdClass', $query->require['_subsite'][1]);
@ -71,7 +79,7 @@ class SearchVariantSubsiteTest extends SapphireTest
//apply the search variant's definition and query //apply the search variant's definition and query
$variant = new SearchVariantSubsites(); $variant = new SearchVariantSubsites();
$variant->alterDefinition('SearchUpdaterTest_Container', $index); $variant->alterDefinition(SearchUpdaterTest_Container::class, $index);
//the protected function isFieldFiltered is implicitly tested here //the protected function isFieldFiltered is implicitly tested here
$variant->alterQuery($query, $index); $variant->alterQuery($query, $index);

View File

@ -28,8 +28,6 @@ class SearchVariantVersionedTest extends SapphireTest
protected function setUp() protected function setUp()
{ {
Config::modify()->set(SearchUpdater::class, 'flush_on_shutdown', false);
parent::setUp(); parent::setUp();
if (self::$index === null) { if (self::$index === null) {
@ -130,4 +128,15 @@ class SearchVariantVersionedTest extends SapphireTest
array('ID' => $item->ID, '_versionedstage' => 'Live') array('ID' => $item->ID, '_versionedstage' => 'Live')
), $index->getAdded(array('ID', '_versionedstage'))); ), $index->getAdded(array('ID', '_versionedstage')));
} }
public function testCanBeDisabledViaConfig()
{
$variant = new SearchVariantVersioned;
Config::modify()->set(SearchVariantVersioned::class, 'enabled', true);
$this->assertTrue($variant->appliesToEnvironment());
Config::modify()->set(SearchVariantVersioned::class, 'enabled', false);
$this->assertFalse($variant->appliesToEnvironment());
}
} }

View File

@ -2,20 +2,32 @@
namespace SilverStripe\FullTextSearch\Tests; namespace SilverStripe\FullTextSearch\Tests;
use Apache_Solr_Document;
use Page;
use SilverStripe\Assets\File;
use SilverStripe\Assets\Image;
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Dev\SapphireTest; use SilverStripe\Dev\SapphireTest;
use SilverStripe\FullTextSearch\Search\FullTextSearch;
use SilverStripe\FullTextSearch\Search\Processors\SearchUpdateImmediateProcessor;
use SilverStripe\FullTextSearch\Search\Processors\SearchUpdateProcessor;
use SilverStripe\FullTextSearch\Search\Updaters\SearchUpdater;
use SilverStripe\FullTextSearch\Search\Variants\SearchVariantSubsites;
use SilverStripe\FullTextSearch\Solr\Services\Solr4Service;
use SilverStripe\FullTextSearch\Tests\SolrIndexSubsitesTest\SolrIndexSubsitesTest_Index; use SilverStripe\FullTextSearch\Tests\SolrIndexSubsitesTest\SolrIndexSubsitesTest_Index;
use SilverStripe\FullTextSearch\Tests\SolrIndexVersionedTest\SolrDocumentMatcher;
if (class_exists('\Phockito')) { use SilverStripe\ORM\DataObject;
\Phockito::include_hamcrest(false); use SilverStripe\Subsites\Model\Subsite;
} use SilverStripe\Versioned\Versioned;
/** /**
* Subsite specific solr testing * Subsite specific solr testing
*/ */
class SolrIndexSubsitesTest extends SapphireTest class SolrIndexSubsitesTest extends SapphireTest
{ {
// @todo protected static $fixture_file = 'SolrIndexSubsitesTest/SolrIndexSubsitesTest.yml';
// protected static $fixture_file = 'SolrIndexSubsitesTest/SolrIndexSubsitesTest.yml';
/** /**
* @var SolrIndexSubsitesTest_Index * @var SolrIndexSubsitesTest_Index
@ -26,31 +38,28 @@ class SolrIndexSubsitesTest extends SapphireTest
protected function setUp() protected function setUp()
{ {
// Prevent parent::setUp() crashing on db build
if (!class_exists(Subsite::class)) {
static::$fixture_file = null;
}
parent::setUp(); parent::setUp();
// Prevent parent::setUp() crashing on db build if (!class_exists(Subsite::class)) {
if (!class_exists('Subsite')) {
$this->skipTest = true;
$this->markTestSkipped("These tests need the Subsite module installed to run"); $this->markTestSkipped("These tests need the Subsite module installed to run");
} }
$this->server = $_SERVER; $this->server = $_SERVER;
if (!class_exists('\Phockito')) {
$this->skipTest = true;
$this->markTestSkipped("These tests need the \Phockito module installed to run");
return;
}
if (self::$index === null) { if (self::$index === null) {
self::$index = singleton('SolrIndexSubsitesTest_Index'); self::$index = singleton(SolrIndexSubsitesTest_Index::class);
} }
SearchUpdater::bind_manipulation_capture(); SearchUpdater::bind_manipulation_capture();
Config::modify()->set('Injector', 'SearchUpdateProcessor', array( Config::modify()->set(Injector::class, SearchUpdateProcessor::class, [
'class' => 'SearchUpdateImmediateProcessor' 'class' => SearchUpdateImmediateProcessor::class,
)); ]);
FullTextSearch::force_index_list(self::$index); FullTextSearch::force_index_list(self::$index);
SearchUpdater::clear_dirty_indexes(); SearchUpdater::clear_dirty_indexes();
@ -67,7 +76,9 @@ class SolrIndexSubsitesTest extends SapphireTest
protected function getServiceMock() protected function getServiceMock()
{ {
return \Phockito::mock('Solr4Service'); return $this->getMockBuilder(Solr4Service::class)
->setMethods(['addDocument', 'commit'])
->getMock();
} }
/** /**
@ -83,7 +94,9 @@ class SolrIndexSubsitesTest extends SapphireTest
$variants = array(); $variants = array();
// Check subsite // Check subsite
if (class_exists('Subsite') && DataObject::getSchema()->hasOneComponent($object->getClassName(), 'Subsite')) { if (class_exists(Subsite::class)
&& DataObject::getSchema()->hasOneComponent($object->getClassName(), 'Subsite')
) {
$variants[] = '"SearchVariantSubsites":"' . $subsiteID. '"'; $variants[] = '"SearchVariantSubsites":"' . $subsiteID. '"';
} }
@ -91,7 +104,7 @@ class SolrIndexSubsitesTest extends SapphireTest
if ($stage) { if ($stage) {
$variants[] = '"SearchVariantVersioned":"' . $stage . '"'; $variants[] = '"SearchVariantVersioned":"' . $stage . '"';
} }
return $id.'-'.$class.'-{'.implode(',', $variants).'}'; return $id . '-' . $class . '-{' . implode(',', $variants) . '}';
} }
public function testPublishing() public function testPublishing()
@ -100,146 +113,163 @@ class SolrIndexSubsitesTest extends SapphireTest
$serviceMock = $this->getServiceMock(); $serviceMock = $this->getServiceMock();
self::$index->setService($serviceMock); self::$index->setService($serviceMock);
$subsite1 = $this->objFromFixture('Subsite', 'subsite1'); $subsite1 = $this->objFromFixture(Subsite::class, 'subsite1');
// Add records to first subsite // Add records to first subsite
Versioned::reading_stage('Stage'); Versioned::set_stage(Versioned::DRAFT);
$_SERVER['HTTP_HOST'] = 'www.subsite1.com'; $_SERVER['HTTP_HOST'] = 'www.subsite1.com';
\Phockito::reset($serviceMock);
$file = new File(); $file = new File();
$file->Title = 'My File'; $file->Title = 'My File';
$file->SubsiteID = $subsite1->ID; $file->SubsiteID = $subsite1->ID;
$file->write(); $file->write();
$page = new Page(); $page = new Page();
$page->Title = 'My Page'; $page->Title = 'My Page';
$page->SubsiteID = $subsite1->ID; $page->SubsiteID = $subsite1->ID;
$page->write(); $page->write();
SearchUpdater::flush_dirty_indexes();
$doc1 = new SolrDocumentMatcher(array( $doc1 = new Apache_Solr_Document([
'_documentid' => $this->getExpectedDocumentId($page, $subsite1->ID, 'Stage'), '_documentid' => $this->getExpectedDocumentId($page, $subsite1->ID, 'Stage'),
'ClassName' => 'Page', 'ClassName' => 'Page',
'SiteTree_Title' => 'My Page', 'SiteTree_Title' => 'My Page',
'_versionedstage' => 'Stage', '_versionedstage' => 'Stage',
'_subsite' => $subsite1->ID '_subsite' => $subsite1->ID,
)); ]);
$doc2 = new SolrDocumentMatcher(array(
$doc2 = new Apache_Solr_Document([
'_documentid' => $this->getExpectedDocumentId($file, $subsite1->ID), '_documentid' => $this->getExpectedDocumentId($file, $subsite1->ID),
'ClassName' => 'File', 'ClassName' => File::class,
'File_Title' => 'My File', 'File_Title' => 'My File',
'_subsite' => $subsite1->ID '_subsite' => $subsite1->ID,
)); ]);
\Phockito::verify($serviceMock)->addDocument($doc1);
\Phockito::verify($serviceMock)->addDocument($doc2); $serviceMock
->expects($this->exactly(2))
->method('addDocument')
->withConsecutive($doc1, $doc2);
SearchUpdater::flush_dirty_indexes();
} }
public function testCorrectSubsiteIDOnPageWrite() public function testCorrectSubsiteIDOnPageWrite()
{ {
$mockWrites = array( $mockWrites = [
'3367:SiteTree:a:1:{s:22:"SearchVariantVersioned";s:4:"Live";}' => array( '3367:SiteTree:a:1:{s:22:"SearchVariantVersioned";s:4:"Live";}' => [
'base' => 'SiteTree', 'base' => 'SilverStripe\\CMS\\Model\\SiteTree',
'class' => 'Page', 'class' => 'Page',
'id' => 3367, 'id' => 3367,
'statefulids' => array( 'statefulids' => [
array( [
'id' => 3367, 'id' => 3367,
'state' => array( 'state' => [
'SearchVariantVersioned' => 'Live', 'SearchVariantVersioned' => 'Live',
), ],
), ],
), ],
'fields' => array( 'fields' => [
'SiteTree:ClassName' => 'Page', 'SilverStripe\\CMS\\Model\\SiteTree:ClassName' => 'Page',
'SiteTree:LastEdited' => '2016-12-08 23:55:30', 'SilverStripe\\CMS\\Model\\SiteTree:LastEdited' => '2016-12-08 23:55:30',
'SiteTree:Created' => '2016-11-30 05:23:58', 'SilverStripe\\CMS\\Model\\SiteTree:Created' => '2016-11-30 05:23:58',
'SiteTree:URLSegment' => 'test', 'SilverStripe\\CMS\\Model\\SiteTree:URLSegment' => 'test',
'SiteTree:Title' => 'Test Title', 'SilverStripe\\CMS\\Model\\SiteTree:Title' => 'Test Title',
'SiteTree:Content' => '<p>test content</p>', 'SilverStripe\\CMS\\Model\\SiteTree:Content' => '<p>test content</p>',
'SiteTree:MetaDescription' => 'a solr test', 'SilverStripe\\CMS\\Model\\SiteTree:MetaDescription' => 'a solr test',
'SiteTree:ShowInMenus' => 1, 'SilverStripe\\CMS\\Model\\SiteTree:ShowInMenus' => 1,
'SiteTree:ShowInSearch' => 1, 'SilverStripe\\CMS\\Model\\SiteTree:ShowInSearch' => 1,
'SiteTree:Sort' => 77, 'SilverStripe\\CMS\\Model\\SiteTree:Sort' => 77,
'SiteTree:HasBrokenFile' => 0, 'SilverStripe\\CMS\\Model\\SiteTree:HasBrokenFile' => 0,
'SiteTree:HasBrokenLink' => 0, 'SilverStripe\\CMS\\Model\\SiteTree:HasBrokenLink' => 0,
'SiteTree:CanViewType' => 'Inherit', 'SilverStripe\\CMS\\Model\\SiteTree:CanViewType' => 'Inherit',
'SiteTree:CanEditType' => 'Inherit', 'SilverStripe\\CMS\\Model\\SiteTree:CanEditType' => 'Inherit',
'SiteTree:Locale' => 'en_NZ', 'SilverStripe\\CMS\\Model\\SiteTree:Locale' => 'en_NZ',
'SiteTree:SubsiteID' => 0, 'SilverStripe\\CMS\\Model\\SiteTree:SubsiteID' => 0,
'Page:ID' => 3367, 'Page:ID' => 3367,
'Page:MetaKeywords' => null, 'Page:MetaKeywords' => null,
), ],
), ],
); ];
$variant = new SearchVariantSubsites(); $variant = new SearchVariantSubsites();
$tmpMockWrites = $mockWrites; $tmpMockWrites = $mockWrites;
$variant->extractManipulationWriteState($tmpMockWrites); $variant->extractManipulationWriteState($tmpMockWrites);
foreach ($tmpMockWrites as $mockWrite) { foreach ($tmpMockWrites as $mockWrite) {
$this->assertCount(1, $mockWrite['statefulids']); $this->assertCount(1, $mockWrite['statefulids']);
$statefulIDs = array_shift($mockWrite['statefulids']); $statefulIDs = array_shift($mockWrite['statefulids']);
$this->assertEquals(0, $statefulIDs['state']['SearchVariantSubsites']);
$this->assertArrayHasKey(SearchVariantSubsites::class, $statefulIDs['state']);
$this->assertEquals(0, $statefulIDs['state'][SearchVariantSubsites::class]);
} }
$subsite = $this->objFromFixture('Subsite', 'subsite1'); $subsite = $this->objFromFixture(Subsite::class, 'subsite1');
$tmpMockWrites = $mockWrites; $tmpMockWrites = $mockWrites;
$tmpMockWrites['3367:SiteTree:a:1:{s:22:"SearchVariantVersioned";s:4:"Live";}']['fields']['SiteTree:SubsiteID'] = $subsite->ID; $tmpMockWrites['3367:SiteTree:a:1:{s:22:"SearchVariantVersioned";s:4:"Live";}']['fields'][SiteTree::class . ':SubsiteID'] = $subsite->ID;
$variant->extractManipulationWriteState($tmpMockWrites); $variant->extractManipulationWriteState($tmpMockWrites);
foreach ($tmpMockWrites as $mockWrite) { foreach ($tmpMockWrites as $mockWrite) {
$this->assertCount(1, $mockWrite['statefulids']); $this->assertCount(1, $mockWrite['statefulids']);
$statefulIDs = array_shift($mockWrite['statefulids']); $statefulIDs = array_shift($mockWrite['statefulids']);
$this->assertEquals($subsite->ID, $statefulIDs['state']['SearchVariantSubsites']);
$this->assertArrayHasKey(SearchVariantSubsites::class, $statefulIDs['state']);
$this->assertEquals($subsite->ID, $statefulIDs['state'][SearchVariantSubsites::class]);
} }
} }
public function testCorrectSubsiteIDOnFileWrite() public function testCorrectSubsiteIDOnFileWrite()
{ {
$subsiteIDs = array('0') + $this->allFixtureIDs('Subsite'); $subsiteIDs = ['0'] + $this->allFixtureIDs(Subsite::class);
$mockWrites = array( $mockWrites = [
'35910:File:a:0:{}' => array( '35910:File:a:0:{}' => [
'base' => 'File', 'base' => File::class,
'class' => 'File', 'class' => File::class,
'id' => 35910, 'id' => 35910,
'statefulids' => array( 'statefulids' => [
array( [
'id' => 35910, 'id' => 35910,
'state' => array(), 'state' => [],
), ],
), ],
'fields' => array( 'fields' => [
'File:ClassName' => 'Image', File::class . ':ClassName' => Image::class,
'File:ShowInSearch' => 1, File::class . ':ShowInSearch' => 1,
'File:ParentID' => 26470, File::class . ':ParentID' => 26470,
'File:Filename' => 'assets/Uploads/pic.jpg', File::class . ':Filename' => 'assets/Uploads/pic.jpg',
'File:Name' => 'pic.jpg', File::class . ':Name' => 'pic.jpg',
'File:Title' => 'pic', File::class . ':Title' => 'pic',
'File:SubsiteID' => 0, File::class . ':SubsiteID' => 0,
'File:OwnerID' => 661, File::class . ':OwnerID' => 661,
'File:CurrentVersionID' => 22038, File::class . ':CurrentVersionID' => 22038,
'File:LastEdited' => '2016-12-09 00:35:13', File::class . ':LastEdited' => '2016-12-09 00:35:13',
), ],
), ],
); ];
$variant = new SearchVariantSubsites(); $variant = new SearchVariantSubsites();
$tmpMockWrites = $mockWrites; $tmpMockWrites = $mockWrites;
$variant->extractManipulationWriteState($tmpMockWrites); $variant->extractManipulationWriteState($tmpMockWrites);
foreach ($tmpMockWrites as $mockWrite) { foreach ($tmpMockWrites as $mockWrite) {
$this->assertCount(count($subsiteIDs), $mockWrite['statefulids']); $this->assertCount(count($subsiteIDs), $mockWrite['statefulids']);
foreach ($mockWrite['statefulids'] as $statefulIDs) { foreach ($mockWrite['statefulids'] as $statefulIDs) {
$this->assertTrue( $this->assertContains(
in_array($statefulIDs['state']['SearchVariantSubsites'], $subsiteIDs), $statefulIDs['state'][SearchVariantSubsites::class],
sprintf('Failed to assert that %s is in list of valid subsites: %s', $statefulIDs['state']['SearchVariantSubsites'], implode(', ', $subsiteIDs)) $subsiteIDs,
sprintf(
'Failed to assert that %s is in list of valid subsites: %s',
$statefulIDs['state'][SearchVariantSubsites::class],
implode(', ', $subsiteIDs)
)
); );
} }
} }
$subsite = $this->objFromFixture('Subsite', 'subsite1'); $subsite = $this->objFromFixture(Subsite::class, 'subsite1');
$tmpMockWrites = $mockWrites; $tmpMockWrites = $mockWrites;
$tmpMockWrites['35910:File:a:0:{}']['fields']['File:SubsiteID'] = $subsite->ID; $tmpMockWrites['35910:File:a:0:{}']['fields'][File::class . ':SubsiteID'] = $subsite->ID;
$variant->extractManipulationWriteState($tmpMockWrites); $variant->extractManipulationWriteState($tmpMockWrites);
foreach ($tmpMockWrites as $mockWrite) { foreach ($tmpMockWrites as $mockWrite) {
$this->assertCount(1, $mockWrite['statefulids']); $this->assertCount(1, $mockWrite['statefulids']);
$statefulIDs = array_shift($mockWrite['statefulids']); $statefulIDs = array_shift($mockWrite['statefulids']);
$this->assertEquals($subsite->ID, $statefulIDs['state']['SearchVariantSubsites']); $this->assertEquals($subsite->ID, $statefulIDs['state'][SearchVariantSubsites::class]);
} }
} }
} }

View File

@ -1,16 +1,17 @@
Subsite: SilverStripe\Subsites\Model\Subsite:
main: main:
Title: Template Title: Template
subsite1: subsite1:
Title: 'Subsite1 Template' Title: 'Subsite1 Template'
subsite2: subsite2:
Title: 'Subsite2 Template' Title: 'Subsite2 Template'
SubsiteDomain:
SilverStripe\Subsites\Model\SubsiteDomain:
subsite1: subsite1:
SubsiteID: =>Subsite.subsite1 SubsiteID: =>SilverStripe\Subsites\Model\Subsite.subsite1
Domain: www.subsite1.com Domain: www.subsite1.com
Protocol: automatic Protocol: automatic
subsite2: subsite2:
SubsiteID: =>Subsite.subsite2 SubsiteID: =>SilverStripe\Subsites\Model\Subsite.subsite2
Domain: www.subsite2.com Domain: www.subsite2.com
Protocol: automatic Protocol: automatic

View File

@ -2,9 +2,9 @@
namespace SilverStripe\FullTextSearch\Tests\SolrIndexSubsitesTest; namespace SilverStripe\FullTextSearch\Tests\SolrIndexSubsitesTest;
use SilverStripe\FullTextSearch\Solr\SolrIndex;
use SilverStripe\Assets\File; use SilverStripe\Assets\File;
use SilverStripe\CMS\Model\SiteTree; use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\FullTextSearch\Solr\SolrIndex;
class SolrIndexSubsitesTest_Index extends SolrIndex class SolrIndexSubsitesTest_Index extends SolrIndex
{ {

View File

@ -7,18 +7,20 @@ use SilverStripe\Core\Environment;
use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Injector\Injector;
use SilverStripe\Core\Kernel; use SilverStripe\Core\Kernel;
use SilverStripe\Dev\SapphireTest; use SilverStripe\Dev\SapphireTest;
use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_AmbiguousRelationIndex; use SilverStripe\FullTextSearch\Search\Queries\SearchQuery;
use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_AmbiguousRelationInheritedIndex; use SilverStripe\FullTextSearch\Search\Variants\SearchVariantSubsites;
use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_FakeIndex; use SilverStripe\FullTextSearch\Solr\Services\Solr3Service;
use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_FakeIndex2;
use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_BoostedIndex;
use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_Container; use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_Container;
use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_HasOne; use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_HasOne;
use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_HasMany; use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_HasMany;
use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_ManyMany; use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_ManyMany;
use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_OtherContainer; use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_OtherContainer;
use SilverStripe\FullTextSearch\Search\Queries\SearchQuery; use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_AmbiguousRelationIndex;
use SilverStripe\FullTextSearch\Solr\Services\Solr3Service; use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_AmbiguousRelationInheritedIndex;
use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_BoostedIndex;
use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_FakeIndex;
use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_FakeIndex2;
use SilverStripe\Subsites\Model\Subsite;
class SolrIndexTest extends SapphireTest class SolrIndexTest extends SapphireTest
{ {
@ -151,6 +153,10 @@ class SolrIndexTest extends SapphireTest
*/ */
public function testBoostedField() public function testBoostedField()
{ {
if (class_exists(Subsite::class)) {
Config::modify()->set(SearchVariantSubsites::class, 'enabled', false);
}
/** @var Solr3Service|PHPUnit_Framework_MockObject_MockObject $serviceMock */ /** @var Solr3Service|PHPUnit_Framework_MockObject_MockObject $serviceMock */
$serviceMock = $this->getMockBuilder(Solr3Service::class) $serviceMock = $this->getMockBuilder(Solr3Service::class)
->setMethods(['search']) ->setMethods(['search'])
@ -162,8 +168,11 @@ class SolrIndexTest extends SapphireTest
$this->equalTo('+term'), $this->equalTo('+term'),
$this->anything(), $this->anything(),
$this->anything(), $this->anything(),
$this->equalTo(['qf' => SearchUpdaterTest_Container::class . '_Field1^1.5 ' . SearchUpdaterTest_Container::class . '_Field2^2.1 _text', $this->equalTo([
'fq' => '+(_versionedstage:"" (*:* -_versionedstage:[* TO *]))']), 'qf' => SearchUpdaterTest_Container::class . '_Field1^1.5 '
. SearchUpdaterTest_Container::class . '_Field2^2.1 _text',
'fq' => '+(_versionedstage:"" (*:* -_versionedstage:[* TO *]))',
]),
$this->anything() $this->anything()
)->willReturn($this->getFakeRawSolrResponse()); )->willReturn($this->getFakeRawSolrResponse());

View File

@ -2,8 +2,10 @@
namespace SilverStripe\FullTextSearch\Tests; namespace SilverStripe\FullTextSearch\Tests;
use Apache_Solr_Document;
use SilverStripe\Dev\SapphireTest; use SilverStripe\Dev\SapphireTest;
use SilverStripe\Core\Config\Config; use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataObject;
use SilverStripe\FullTextSearch\Search\FullTextSearch; use SilverStripe\FullTextSearch\Search\FullTextSearch;
use SilverStripe\FullTextSearch\Search\SearchIntrospection; use SilverStripe\FullTextSearch\Search\SearchIntrospection;
@ -15,23 +17,28 @@ use SilverStripe\FullTextSearch\Tests\SolrIndexVersionedTest\SolrVersionedTest_I
use SilverStripe\FullTextSearch\Search\Processors\SearchUpdateProcessor; use SilverStripe\FullTextSearch\Search\Processors\SearchUpdateProcessor;
use SilverStripe\FullTextSearch\Search\Processors\SearchUpdateImmediateProcessor; use SilverStripe\FullTextSearch\Search\Processors\SearchUpdateImmediateProcessor;
use SilverStripe\FullTextSearch\Search\Updaters\SearchUpdater; use SilverStripe\FullTextSearch\Search\Updaters\SearchUpdater;
use SilverStripe\FullTextSearch\Search\Variants\SearchVariantSubsites;
use SilverStripe\FullTextSearch\Search\Variants\SearchVariantVersioned; use SilverStripe\FullTextSearch\Search\Variants\SearchVariantVersioned;
use SilverStripe\Subsites\Model\Subsite;
use SilverStripe\Versioned\Versioned; use SilverStripe\Versioned\Versioned;
class SolrIndexVersionedTest extends SapphireTest class SolrIndexVersionedTest extends SapphireTest
{ {
protected $usesDatabase = true;
protected $oldMode = null; protected $oldMode = null;
protected static $index = null; protected static $index = null;
protected static $extra_dataobjects = array( protected static $extra_dataobjects = [
SearchVariantVersionedTest_Item::class, SearchVariantVersionedTest_Item::class,
SolrIndexVersionedTest_Object::class SolrIndexVersionedTest_Object::class,
); ];
protected function setUp() protected function setUp()
{ {
Config::modify()->set(SearchUpdater::class, 'flush_on_shutdown', false); // Need to be set before parent::setUp() since they're executed before the tests start
Config::modify()->set(SearchVariantSubsites::class, 'enabled', false);
parent::setUp(); parent::setUp();
@ -41,18 +48,18 @@ class SolrIndexVersionedTest extends SapphireTest
SearchUpdater::bind_manipulation_capture(); SearchUpdater::bind_manipulation_capture();
Config::modify()->set(Injector::class, SearchUpdateProcessor::class, array( Config::modify()->set(Injector::class, SearchUpdateProcessor::class, [
'class' => SearchUpdateImmediateProcessor::class 'class' => SearchUpdateImmediateProcessor::class
)); ]);
FullTextSearch::force_index_list(self::$index); FullTextSearch::force_index_list(self::$index);
SearchUpdater::clear_dirty_indexes(); SearchUpdater::clear_dirty_indexes();
$this->oldMode = Versioned::get_reading_mode(); $this->oldMode = Versioned::get_reading_mode();
Versioned::set_stage('Stage'); Versioned::set_stage(Versioned::DRAFT);
} }
public function tearDown() protected function tearDown()
{ {
Versioned::set_reading_mode($this->oldMode); Versioned::set_reading_mode($this->oldMode);
parent::tearDown(); parent::tearDown();
@ -80,13 +87,7 @@ class SolrIndexVersionedTest extends SapphireTest
{ {
$id = $object->ID; $id = $object->ID;
$class = DataObject::getSchema()->baseDataClass($object); $class = DataObject::getSchema()->baseDataClass($object);
// Prevent subsites from breaking tests return $id.'-'.$class.'-{'.json_encode(SearchVariantVersioned::class).':"'.$stage.'"}';
// TODO: Subsites currently isn't migrated. This needs to be fixed when subsites is fixed.
$subsites = '';
if (class_exists('Subsite') && DataObject::getSchema()->hasOneComponent($object->getClassName(), 'Subsite')) {
$subsites = '"SearchVariantSubsites":"0",';
}
return $id.'-'.$class.'-{'.$subsites. json_encode(SearchVariantVersioned::class) . ':"'.$stage.'"}';
} }
/** /**
@ -98,12 +99,12 @@ class SolrIndexVersionedTest extends SapphireTest
*/ */
protected function getSolrDocument($class, $object, $value, $stage) protected function getSolrDocument($class, $object, $value, $stage)
{ {
$doc = new \Apache_Solr_Document(); $doc = new Apache_Solr_Document();
$doc->setField('_documentid', $this->getExpectedDocumentId($object, $stage)); $doc->setField('_documentid', $this->getExpectedDocumentId($object, $stage));
$doc->setField('ClassName', $class); $doc->setField('ClassName', $class);
$doc->setField(DataObject::getSchema()->baseDataClass($class) . '_TestText', $value); $doc->setField(DataObject::getSchema()->baseDataClass($class) . '_TestText', $value);
$doc->setField('_versionedstage', $stage); $doc->setField('_versionedstage', $stage);
$doc->setField('ID', $object->ID); $doc->setField('ID', (int) $object->ID);
$doc->setField('ClassHierarchy', SearchIntrospection::hierarchy($class)); $doc->setField('ClassHierarchy', SearchIntrospection::hierarchy($class));
$doc->setFieldBoost('ID', false); $doc->setFieldBoost('ID', false);
$doc->setFieldBoost('ClassHierarchy', false); $doc->setFieldBoost('ClassHierarchy', false);
@ -114,89 +115,52 @@ class SolrIndexVersionedTest extends SapphireTest
public function testPublishing() public function testPublishing()
{ {
// Check that write updates Stage // Check that write updates Stage
Versioned::set_stage('Stage'); Versioned::set_stage(Versioned::DRAFT);
$item = new SearchVariantVersionedTest_Item(array('TestText' => 'Foo')); $item = new SearchVariantVersionedTest_Item(array('TestText' => 'Foo'));
$item->write(); $item->write();
$object = new SolrIndexVersionedTest_Object(array('TestText' => 'Bar')); $object = new SolrIndexVersionedTest_Object(array('TestText' => 'Bar'));
$object->write(); $object->write();
$doc1 = $this->getSolrDocument(SearchVariantVersionedTest_Item::class, $item, 'Foo', 'Stage'); $doc1 = $this->getSolrDocument(SearchVariantVersionedTest_Item::class, $item, 'Foo', Versioned::DRAFT);
$doc2 = $this->getSolrDocument(SolrIndexVersionedTest_Object::class, $object, 'Bar', 'Stage'); $doc2 = $this->getSolrDocument(SolrIndexVersionedTest_Object::class, $object, 'Bar', Versioned::DRAFT);
// Ensure correct call is made to Solr // Ensure correct call is made to Solr
$this->getServiceMock(['addDocument', 'commit']) $this->getServiceMock(['addDocument', 'commit'])
->expects($this->exactly(2)) ->expects($this->exactly(2))
->method('addDocument') ->method('addDocument')
->withConsecutive( ->withConsecutive(
[ [$this->equalTo($doc1)],
$this->equalTo($doc1), [$this->equalTo($doc2)]
$this->anything(),
$this->anything(),
$this->anything(),
$this->anything()
],
[
$this->equalTo($doc2),
$this->anything(),
$this->anything(),
$this->anything(),
$this->anything()
]
); );
SearchUpdater::flush_dirty_indexes(); SearchUpdater::flush_dirty_indexes();
// Check that write updates Live // Check that write updates Live
Versioned::set_stage('Stage'); Versioned::set_stage(Versioned::DRAFT);
$item = new SearchVariantVersionedTest_Item(array('TestText' => 'Foo')); $item = new SearchVariantVersionedTest_Item(array('TestText' => 'Foo'));
$item->write(); $item->write();
$item->copyVersionToStage('Stage', 'Live'); $item->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
$object = new SolrIndexVersionedTest_Object(array('TestText' => 'Bar')); $object = new SolrIndexVersionedTest_Object(array('TestText' => 'Bar'));
$object->write(); $object->write();
$object->copyVersionToStage('Stage', 'Live'); $object->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
$doc1 = $this->getSolrDocument(SearchVariantVersionedTest_Item::class, $item, 'Foo', 'Stage'); $doc1 = $this->getSolrDocument(SearchVariantVersionedTest_Item::class, $item, 'Foo', Versioned::DRAFT);
$doc2 = $this->getSolrDocument(SearchVariantVersionedTest_Item::class, $item, 'Foo', 'Live'); $doc2 = $this->getSolrDocument(SearchVariantVersionedTest_Item::class, $item, 'Foo', Versioned::LIVE);
$doc3 = $this->getSolrDocument(SolrIndexVersionedTest_Object::class, $object, 'Bar', 'Stage'); $doc3 = $this->getSolrDocument(SolrIndexVersionedTest_Object::class, $object, 'Bar', Versioned::DRAFT);
$doc4 = $this->getSolrDocument(SolrIndexVersionedTest_Object::class, $object, 'Bar', 'Live'); $doc4 = $this->getSolrDocument(SolrIndexVersionedTest_Object::class, $object, 'Bar', Versioned::LIVE);
// Ensure correct call is made to Solr // Ensure correct call is made to Solr
$this->getServiceMock(['addDocument', 'commit']) $this->getServiceMock(['addDocument', 'commit'])
->expects($this->exactly(4)) ->expects($this->exactly(4))
->method('addDocument') ->method('addDocument')
->withConsecutive( ->withConsecutive(
[ [$doc1],
$this->equalTo($doc1), [$doc2],
$this->anything(), [$doc3],
$this->anything(), [$doc4]
$this->anything(),
$this->anything()
],
[
$this->equalTo($doc2),
$this->anything(),
$this->anything(),
$this->anything(),
$this->anything()
],
[
$this->equalTo($doc3),
$this->anything(),
$this->anything(),
$this->anything(),
$this->anything()
],
[
$this->equalTo($doc4),
$this->anything(),
$this->anything(),
$this->anything(),
$this->anything()
]
); );
SearchUpdater::flush_dirty_indexes(); SearchUpdater::flush_dirty_indexes();
@ -205,12 +169,12 @@ class SolrIndexVersionedTest extends SapphireTest
public function testDelete() public function testDelete()
{ {
// Delete the live record (not the stage) // Delete the live record (not the stage)
Versioned::set_stage('Stage'); Versioned::set_stage(Versioned::DRAFT);
$item = new SearchVariantVersionedTest_Item(array('TestText' => 'Too')); $item = new SearchVariantVersionedTest_Item(array('TestText' => 'Too'));
$item->write(); $item->write();
$item->copyVersionToStage('Stage', 'Live'); $item->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
Versioned::set_stage('Live'); Versioned::set_stage(Versioned::LIVE);
$id = clone $item; $id = clone $item;
$item->delete(); $item->delete();
@ -218,16 +182,16 @@ class SolrIndexVersionedTest extends SapphireTest
$this->getServiceMock(['addDocument', 'commit', 'deleteById']) $this->getServiceMock(['addDocument', 'commit', 'deleteById'])
->expects($this->exactly(1)) ->expects($this->exactly(1))
->method('deleteById') ->method('deleteById')
->with($this->equalTo($this->getExpectedDocumentId($id, 'Live'))); ->with($this->getExpectedDocumentId($id, Versioned::LIVE));
SearchUpdater::flush_dirty_indexes(); SearchUpdater::flush_dirty_indexes();
// Delete the stage record // Delete the stage record
Versioned::set_stage('Stage'); Versioned::set_stage(Versioned::DRAFT);
$item = new SearchVariantVersionedTest_Item(array('TestText' => 'Too')); $item = new SearchVariantVersionedTest_Item(array('TestText' => 'Too'));
$item->write(); $item->write();
$item->copyVersionToStage('Stage', 'Live'); $item->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
$id = clone $item; $id = clone $item;
$item->delete(); $item->delete();
@ -235,7 +199,7 @@ class SolrIndexVersionedTest extends SapphireTest
$this->getServiceMock(['addDocument', 'commit', 'deleteById']) $this->getServiceMock(['addDocument', 'commit', 'deleteById'])
->expects($this->exactly(1)) ->expects($this->exactly(1))
->method('deleteById') ->method('deleteById')
->with($this->equalTo($this->getExpectedDocumentId($id, 'Stage'))); ->with($this->getExpectedDocumentId($id, Versioned::DRAFT));
SearchUpdater::flush_dirty_indexes(); SearchUpdater::flush_dirty_indexes();
} }

View File

@ -1,39 +0,0 @@
<?php
namespace SilverStripe\FullTextSearch\Tests\SolrIndexVersionedTest;
if (!class_exists('\Phockito')) {
return;
}
\Phockito::include_hamcrest(false);
class SolrDocumentMatcher extends \Hamcrest_BaseMatcher
{
protected $properties;
public function __construct($properties)
{
$this->properties = $properties;
}
public function describeTo(\Hamcrest_Description $description)
{
$description->appendText('\Apache_Solr_Document with properties '.var_export($this->properties, true));
}
public function matches($item)
{
if (! ($item instanceof \Apache_Solr_Document)) {
return false;
}
foreach ($this->properties as $key => $value) {
if ($item->{$key} != $value) {
return false;
}
}
return true;
}
}

View File

@ -49,17 +49,13 @@ class SolrReindexQueuedTest extends SapphireTest
protected function setUp() protected function setUp()
{ {
Config::modify()->set(SearchUpdater::class, 'flush_on_shutdown', false);
parent::setUp(); parent::setUp();
if (!interface_exists('Symbiote\QueuedJobs\Services\QueuedJob')) { if (!interface_exists(QueuedJob::class)) {
$this->skipTest = true; $this->skipTest = true;
return $this->markTestSkipped("These tests need the QueuedJobs module installed to run"); return $this->markTestSkipped("These tests need the QueuedJobs module installed to run");
} }
Config::modify()->set(QueuedJobService::class, 'use_shutdown_function', false);
// Set queued handler for reindex // Set queued handler for reindex
Config::modify()->set(Injector::class, SolrReindexHandler::class, array( Config::modify()->set(Injector::class, SolrReindexHandler::class, array(
'class' => SolrReindexQueuedHandler::class 'class' => SolrReindexQueuedHandler::class

View File

@ -43,8 +43,6 @@ class SolrReindexTest extends SapphireTest
protected function setUp() protected function setUp()
{ {
Config::modify()->set(SearchUpdater::class, 'flush_on_shutdown', false);
parent::setUp(); parent::setUp();
// Set test handler for reindex // Set test handler for reindex

View File

@ -0,0 +1,37 @@
<?php
namespace SilverStripe\FullTextSearch\Tests\State;
use SilverStripe\Core\Config\Config;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Dev\State\TestState;
use SilverStripe\FullTextSearch\Search\Updaters\SearchUpdater;
use SilverStripe\FullTextSearch\Search\Variants\SearchVariant;
use Symbiote\QueuedJobs\Services\QueuedJobService;
class FullTextSearchState implements TestState
{
public function setUp(SapphireTest $test)
{
// noop
}
public function tearDown(SapphireTest $test)
{
SearchVariant::clear_variant_cache();
}
public function setUpOnce($class)
{
Config::modify()->set(SearchUpdater::class, 'flush_on_shutdown', false);
if (class_exists(QueuedJobService::class)) {
Config::modify()->set(QueuedJobService::class, 'use_shutdown_function', false);
}
}
public function tearDownOnce($class)
{
// noop
}
}