* @copyright 2018
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
*/
namespace OCA\FullTextSearch\Command;
use Exception;
use OC\Core\Command\InterruptedException;
use OCA\FullTextSearch\ACommandBase;
use OCA\FullTextSearch\Exceptions\InterruptException;
use OCA\FullTextSearch\Exceptions\ProviderDoesNotExistException;
use OCA\FullTextSearch\Exceptions\ProviderIsNotCompatibleException;
use OCA\FullTextSearch\Exceptions\ProviderIsNotUniqueException;
use OCA\FullTextSearch\Exceptions\RunnerAlreadyUpException;
use OCA\FullTextSearch\Exceptions\TickDoesNotExistException;
use OCA\FullTextSearch\Model\IndexOptions;
use OCA\FullTextSearch\Model\Runner;
use OCA\FullTextSearch\Model\SearchRequest;
use OCA\FullTextSearch\Model\SearchResult;
use OCA\FullTextSearch\Provider\TestProvider;
use OCA\FullTextSearch\Service\IndexService;
use OCA\FullTextSearch\Service\MiscService;
use OCA\FullTextSearch\Service\PlatformService;
use OCA\FullTextSearch\Service\ProviderService;
use OCA\FullTextSearch\Service\RunningService;
use OCA\FullTextSearch\Service\TestService;
use OCP\AppFramework\QueryException;
use OCP\FullTextSearch\IFullTextSearchPlatform;
use OCP\FullTextSearch\IFullTextSearchProvider;
use OCP\FullTextSearch\Model\DocumentAccess;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Class Test
*
* @package OCA\FullTextSearch\Command
*/
class Test extends ACommandBase {
const DELAY_STABILIZE_PLATFORM = 3;
/** @var RunningService */
private $runningService;
/** @var PlatformService */
private $platformService;
/** @var ProviderService */
private $providerService;
/** @var IndexService */
private $indexService;
/** @var TestService */
private $testService;
/** @var MiscService */
private $miscService;
/** @var Runner */
private $runner;
/** @var boolean */
private $isJson = false;
/**
* Index constructor.
*
* @param RunningService $runningService
* @param ProviderService $providerService
* @param IndexService $indexService
* @param PlatformService $platformService
* @param TestService $testService
* @param MiscService $miscService
*/
public function __construct(
RunningService $runningService, PlatformService $platformService,
ProviderService $providerService, IndexService $indexService, TestService $testService,
MiscService $miscService
) {
parent::__construct();
$this->runningService = $runningService;
$this->platformService = $platformService;
$this->providerService = $providerService;
$this->indexService = $indexService;
$this->testService = $testService;
$this->miscService = $miscService;
}
/**
*
*/
protected function configure() {
parent::configure();
$this->setName('fulltextsearch:test')
->setDescription('Testing the platform setup')
->addOption('json', 'j', InputOption::VALUE_NONE, 'return result as JSON')
->addOption(
'platform_delay', 'd', InputOption::VALUE_REQUIRED,
'change DELAY_STABILIZE_PLATFORM'
);
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @throws Exception
*/
protected function execute(InputInterface $input, OutputInterface $output) {
$this->isJson = ($input->getOption('json') === true);
$platformDelay = ($input->getOption('platform_delay') > 0) ? (int)$input->getOption(
'platform_delay'
) : self::DELAY_STABILIZE_PLATFORM;
$this->output($output, '.Testing your current setup:');
try {
$testProvider = $this->testCreatingProvider($output);
$this->testMockedProvider($output, $testProvider);
$testPlatform = $this->testLoadingPlatform($output);
$this->testLockingProcess($output, $testPlatform, $testProvider);
} catch (Exception $e) {
$this->outputResult($output, false);
throw $e;
}
try {
$this->testResetTest($output, $testProvider);
$this->pause($output, $platformDelay);
$this->testInitIndexing($output, $testPlatform);
$this->testIndexingDocuments($output, $testPlatform, $testProvider);
$this->pause($output, $platformDelay);
$this->testContentLicense($output, $testPlatform);
$this->testSearchSimple($output, $testPlatform, $testProvider);
$this->testUpdatingDocumentsAccess($output, $testPlatform, $testProvider);
$this->pause($output, $platformDelay);
$this->testSearchAccess($output, $testPlatform, $testProvider);
$this->testSearchShare($output, $testPlatform, $testProvider);
$this->testResetTest($output, $testProvider);
$this->testUnlockingProcess($output);
} catch (Exception $e) {
$this->outputResult($output, false);
$this->output($output, 'Error detected, unlocking process');
$this->runner->stop();
$this->outputResult($output, true);
throw $e;
}
$this->output($output, '', true);
}
/**
* @return IFullTextSearchProvider
* @throws ProviderIsNotCompatibleException
* @throws QueryException
* @throws ProviderDoesNotExistException
* @throws ProviderIsNotUniqueException
*/
private function generateMockProvider(): IFullTextSearchProvider {
$this->providerService->loadProvider(
'fulltextsearch', 'OCA\FullTextSearch\Provider\TestProvider'
);
$providerWrapper = $this->providerService->getProvider(TestProvider::TEST_PROVIDER_ID);
return $providerWrapper->getProvider();
}
/**
* @param OutputInterface $output
* @param string $line
* @param bool $isNewLine
*/
private function output(OutputInterface $output, string $line, bool $isNewLine = true) {
if ($isNewLine) {
$output->write(' ', true);
}
$output->write($line . ' ', false);
}
/**
* @param OutputInterface $output
* @param bool $result
*/
private function outputResult(OutputInterface $output, bool $result) {
$isNewLine = false;
$line = $this->convertBoolToLine($result, $isNewLine);
$this->output($output, $line, $isNewLine);
}
/**
* @param bool $result
* @param bool $isNewLine
*
* @return string
*/
private function convertBoolToLine(bool $result, bool &$isNewLine): string {
$isNewLine = false;
if ($result === false) {
return 'fail';
}
return 'ok';
}
/**
* @param OutputInterface $output
*
* @return IFullTextSearchProvider
* @throws ProviderDoesNotExistException
* @throws ProviderIsNotCompatibleException
* @throws ProviderIsNotUniqueException
* @throws QueryException
*/
private function testCreatingProvider(OutputInterface $output): IFullTextSearchProvider {
$this->output($output, 'Creating mocked content provider.');
$testProvider = $this->generateMockProvider();
$this->outputResult($output, true);
return $testProvider;
}
/**
* @param OutputInterface $output
* @param IFullTextSearchProvider $testProvider
*/
private function testMockedProvider(
OutputInterface $output, IFullTextSearchProvider $testProvider
) {
$this->output($output, 'Testing mocked provider: get indexable documents.');
$testProvider->setIndexOptions(new IndexOptions());
$indexableDocuments =
$testProvider->generateIndexableDocuments(TestService::DOCUMENT_USER1);
$this->output($output, '(' . sizeof($indexableDocuments) . ' items)', false);
$this->outputResult($output, true);
}
/**
* @param OutputInterface $output
*
* @return IFullTextSearchPlatform
* @throws Exception
*/
private function testLoadingPlatform(OutputInterface $output): IFullTextSearchPlatform {
$this->output($output, 'Loading search platform.');
$wrapper = $this->platformService->getPlatform();
$testPlatform = $wrapper->getPlatform();
$this->output($output, '(' . $testPlatform->getName() . ')', false);
$this->outputResult($output, true);
$this->output($output, 'Testing search platform.');
if (!$testPlatform->testPlatform()) {
throw new Exception ('Search platform (' . $testPlatform->getName() . ') down ?');
}
$this->outputResult($output, true);
return $testPlatform;
}
/**
* @param OutputInterface $output
* @param IFullTextSearchPlatform $testPlatform
* @param IFullTextSearchProvider $testProvider
*
* @throws RunnerAlreadyUpException
*/
private function testLockingProcess(
OutputInterface $output, IFullTextSearchPlatform $testPlatform,
IFullTextSearchProvider $testProvider
) {
$this->output($output, 'Locking process');
$this->runner = new Runner($this->runningService, 'test');
$this->runner->sourceIsCommandLine($this, $output);
$this->runner->start();
$this->indexService->setRunner($this->runner);
$testPlatform->setRunner($this->runner);
$testProvider->setRunner($this->runner);
$this->outputResult($output, true);
}
/**
* @param OutputInterface $output
* @param IFullTextSearchProvider $testProvider
*
* @throws Exception
*/
private function testResetTest(OutputInterface $output, IFullTextSearchProvider $testProvider
) {
$this->output($output, 'Removing test.');
$this->indexService->resetIndex($testProvider->getId());
$this->outputResult($output, true);
}
/**
* @param OutputInterface $output
* @param IFullTextSearchPlatform $testPlatform
*/
private function testInitIndexing(OutputInterface $output, IFullTextSearchPlatform $testPlatform
) {
$this->output($output, 'Initializing index mapping.');
$testPlatform->initializeIndex();
$this->outputResult($output, true);
}
/**
* @param OutputInterface $output
* @param IFullTextSearchPlatform $testPlatform
* @param IFullTextSearchProvider $testProvider
*
* @throws Exception
*/
private function testIndexingDocuments(
OutputInterface $output, IFullTextSearchPlatform $testPlatform,
IFullTextSearchProvider $testProvider
) {
$this->output($output, 'Indexing generated documents.');
$options = new IndexOptions(
[
'provider' => TestProvider::TEST_PROVIDER_ID
]
);
$this->indexService->indexProviderContentFromUser(
$testPlatform, $testProvider, TestService::DOCUMENT_USER1, $options
);
$this->outputResult($output, true);
}
/**
* @param OutputInterface $output
* @param IFullTextSearchPlatform $testPlatform
*
* @throws Exception
*/
private function testContentLicense(
OutputInterface $output, IFullTextSearchPlatform $testPlatform
) {
try {
$this->output($output, 'Retreiving content from a big index (license).');
$indexDocument = $testPlatform->getDocument(
TestProvider::TEST_PROVIDER_ID, TestService::DOCUMENT_TYPE_LICENSE
);
$this->output(
$output, '(size: ' . $indexDocument->getContentSize() . ')', false
);
$this->outputResult($output, true);
} catch (Exception $e) {
throw new Exception(
"Issue while getting test document '" . TestService::DOCUMENT_TYPE_LICENSE
. "' from search platform: " . $e->getMessage()
);
}
$this->output($output, 'Comparing document with source.');
$this->testService->compareIndexDocument(
$this->testService->generateIndexDocumentContentLicense(new IndexOptions()),
$indexDocument
);
$this->outputResult($output, true);
}
/**
* @param OutputInterface $output
* @param IFullTextSearchPlatform $testPlatform
* @param IFullTextSearchProvider $testProvider
*
* @throws Exception
*/
private function testSearchSimple(
OutputInterface $output, IFullTextSearchPlatform $testPlatform,
IFullTextSearchProvider $testProvider
) {
$this->output($output, 'Searching basic keywords:');
$access = new DocumentAccess();
$access->setViewerId(TestService::DOCUMENT_USER1);
$this->search(
$output, $testPlatform, $testProvider, $access, 'test',
[TestService::DOCUMENT_TYPE_SIMPLE]
);
$this->search(
$output, $testPlatform, $testProvider, $access, 'document is a simple test',
// [TestService::DOCUMENT_TYPE_SIMPLE]
[TestService::DOCUMENT_TYPE_SIMPLE, TestService::DOCUMENT_TYPE_LICENSE]
);
$this->search(
$output, $testPlatform, $testProvider, $access, '"document is a test"',
[]
);
$this->search(
$output, $testPlatform, $testProvider, $access, '"document is a simple test"',
[TestService::DOCUMENT_TYPE_SIMPLE]
);
$this->search(
$output, $testPlatform, $testProvider, $access, 'document is a simple -test',
[TestService::DOCUMENT_TYPE_LICENSE]
);
$this->search(
$output, $testPlatform, $testProvider, $access, 'document is a simple +test',
[TestService::DOCUMENT_TYPE_SIMPLE]
);
$this->search(
$output, $testPlatform, $testProvider, $access, '-document is a simple test',
[]
);
}
/**
* @param OutputInterface $output
* @param IFullTextSearchPlatform $testPlatform
* @param IFullTextSearchProvider $testProvider
*
* @throws Exception
*/
private function testUpdatingDocumentsAccess(
OutputInterface $output, IFullTextSearchPlatform $testPlatform,
IFullTextSearchProvider $testProvider
) {
$this->output($output, 'Updating documents access.');
$options = new IndexOptions(
[
'provider' => TestProvider::TEST_PROVIDER_ID,
TestService::DOCUMENT_INDEXING_OPTION => TestService::DOCUMENT_INDEXING_ACCESS
]
);
$testProvider->setIndexOptions($options);
$this->indexService->indexProviderContentFromUser(
$testPlatform, $testProvider, TestService::DOCUMENT_USER1, $options
);
$this->outputResult($output, true);
}
/**
* @param OutputInterface $output
* @param IFullTextSearchPlatform $platform
* @param IFullTextSearchProvider $provider
*
* @throws Exception
*/
private function testSearchAccess(
OutputInterface $output, IFullTextSearchPlatform $platform,
IFullTextSearchProvider $provider
) {
$this->output($output, 'Searching with group access rights:');
$this->searchGroups($output, $platform, $provider, [], []);
$this->searchGroups(
$output, $platform, $provider, [TestService::DOCUMENT_GROUP1],
[TestService::DOCUMENT_TYPE_LICENSE]
);
$this->searchGroups(
$output, $platform, $provider,
[TestService::DOCUMENT_GROUP1, TestService::DOCUMENT_GROUP2],
[TestService::DOCUMENT_TYPE_LICENSE]
);
$this->searchGroups(
$output, $platform, $provider,
[TestService::DOCUMENT_NOTGROUP, TestService::DOCUMENT_GROUP2],
[TestService::DOCUMENT_TYPE_LICENSE]
);
$this->searchGroups($output, $platform, $provider, [TestService::DOCUMENT_NOTGROUP], []);
}
/**
* @param OutputInterface $output
* @param IFullTextSearchPlatform $platform
* @param IFullTextSearchProvider $provider
*
* @throws Exception
*/
private function testSearchShare(
OutputInterface $output, IFullTextSearchPlatform $platform,
IFullTextSearchProvider $provider
) {
$this->output($output, 'Searching with share rights:');
$this->searchUsers($output, $platform, $provider, TestService::DOCUMENT_NOTUSER, []);
$this->searchUsers($output, $platform, $provider, TestService::DOCUMENT_USER2, ['license']);
$this->searchUsers($output, $platform, $provider, TestService::DOCUMENT_USER3, ['license']);
}
/**
* @param OutputInterface $output
*
* @throws TickDoesNotExistException
*/
private function testUnlockingProcess(OutputInterface $output) {
$this->output($output, 'Unlocking process');
$this->runner->stop();
$this->outputResult($output, true);
}
/**
* @param OutputInterface $output
* @param IFullTextSearchPlatform $testPlatform
* @param IFullTextSearchProvider $testProvider
* @param DocumentAccess $access
* @param string $search
* @param array $expected
* @param string $moreOutput
*
* @throws Exception
*/
private function search(
OutputInterface $output, IFullTextSearchPlatform $testPlatform,
IFullTextSearchProvider $testProvider,
DocumentAccess $access, string $search, array $expected, string $moreOutput = ''
) {
$this->output(
$output,
" - '" . $search . "'" . (($moreOutput === '') ? '' : ' - ' . $moreOutput . ' - ')
);
$request = new SearchRequest();
$request->setSearch($search);
$searchResult = new SearchResult($request);
$searchResult->setProvider($testProvider);
$searchResult->setPlatform($testPlatform);
$testPlatform->searchRequest($searchResult, $access);
$this->output(
$output,
'(result: ' . $searchResult->getCount() . ', expected: ' . json_encode($expected) . ')',
false
);
$this->compareSearchResult($searchResult, $expected);
$this->outputResult($output, true);
}
/**
* @param OutputInterface $output
* @param IFullTextSearchPlatform $testPlatform
* @param IFullTextSearchProvider $testProvider
* @param array $groups
* @param array $expected
*
* @throws Exception
*/
private function searchGroups(
OutputInterface $output, IFullTextSearchPlatform $testPlatform,
IFullTextSearchProvider $testProvider, array $groups, array $expected
) {
$access = new DocumentAccess();
$access->setViewerId(TestService::DOCUMENT_NOTUSER);
$access->setGroups($groups);
$this->search(
$output, $testPlatform, $testProvider, $access, 'license',
$expected, json_encode($groups)
);
}
/**
* @param OutputInterface $output
* @param IFullTextSearchPlatform $testPlatform
* @param IFullTextSearchProvider $testProvider
* @param string $user
* @param array $expected
*
* @throws Exception
*/
private function searchUsers(
OutputInterface $output, IFullTextSearchPlatform $testPlatform,
IFullTextSearchProvider $testProvider, string $user, array $expected
) {
$access = new DocumentAccess();
$access->setViewerId($user);
$this->search(
$output, $testPlatform, $testProvider, $access, 'license',
$expected, $user
);
}
/**
* @param SearchResult $searchResult
* @param array $entries
*
* @throws Exception
*/
private function compareSearchResult(SearchResult $searchResult, array $entries) {
$documents = $searchResult->getDocuments();
if (sizeof($documents) !== sizeof($entries)) {
throw new \Exception('Unexpected SearchResult: ' . json_encode($searchResult));
}
foreach ($documents as $document) {
if (!in_array($document->getId(), $entries)) {
throw new \Exception('Unexpected Document: ' . json_encode($document));
}
}
}
/**
* @param OutputInterface $output
* @param int $s
*
* @throws InterruptException
*/
private function pause(OutputInterface $output, int $s) {
$this->output($output, 'Pausing ' . $s . ' seconds');
for ($i = 1; $i <= $s; $i++) {
if (time_nanosleep(1, 0) !== true) {
throw new InterruptException('Interrupted by user');
}
$this->output($output, (string)$i, false);
}
$this->outputResult($output, true);
}
/**
* @throws TickDoesNotExistException
*/
public function abort() {
try {
$this->abortIfInterrupted();
} catch (InterruptedException $e) {
$this->runner->stop();
exit();
}
}
}