at the end of the day, it was inevitable
This commit is contained in:
@@ -0,0 +1,232 @@
|
||||
<?php
|
||||
|
||||
namespace Api\Context;
|
||||
|
||||
use Api\Util\ApiConnection;
|
||||
use Behat\Gherkin\Node\PyStringNode;
|
||||
use Behat\Gherkin\Node\TableNode;
|
||||
use Coduo\PHPMatcher\PHPMatcher;
|
||||
use Common\Context\AbstractContext;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpKernel\Profiler\Profiler;
|
||||
|
||||
/**
|
||||
* Class ApiContext
|
||||
* @package Api\Context
|
||||
*/
|
||||
class ApiContext extends AbstractContext
|
||||
{
|
||||
|
||||
/**
|
||||
* @var ApiConnection
|
||||
*/
|
||||
private $apiConnection;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $authToken;
|
||||
|
||||
/**
|
||||
* @param ContainerInterface $container A ContainerInterface instance.
|
||||
* @param string $baseUrl Base url to app api.
|
||||
* @param string $fixturesDir Path to fixtures directory.
|
||||
*/
|
||||
public function __construct(
|
||||
ContainerInterface $container,
|
||||
$baseUrl,
|
||||
$fixturesDir
|
||||
) {
|
||||
parent::__construct($container, $fixturesDir);
|
||||
|
||||
$this->apiConnection = new ApiConnection($baseUrl, $this->debug);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make request to api.
|
||||
*
|
||||
* @Given /^(?:|I )[Mm]ake (?P<method>GET|POST|PUT|DELETE) request to (?P<endpoint>.+)$/
|
||||
*
|
||||
* @param string $method HTTP method name.
|
||||
* @param string $endpoint Relative to base url.
|
||||
* @param mixed $payload Request parameters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function request($method, $endpoint, $payload = null)
|
||||
{
|
||||
switch (true) {
|
||||
case $payload instanceof TableNode:
|
||||
$table = $payload->getTable();
|
||||
$payload = [];
|
||||
|
||||
foreach ($table as $row) {
|
||||
$payload[current($row)] = next($row);
|
||||
}
|
||||
break;
|
||||
|
||||
case $payload instanceof PyStringNode:
|
||||
$payload = (array) json_decode($payload->getRaw(), true);
|
||||
$payload = array_filter($payload);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Request error: ' . json_last_error_msg()
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
$payload = [];
|
||||
}
|
||||
|
||||
$payload = $this->processor->process($payload);
|
||||
|
||||
$this->apiConnection->request($method, $endpoint, $payload, $this->authToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^(?:|I )[Gg]ot response with code (?P<code>\d+)$/
|
||||
* @Then /^(?:|I )[Gg]ot successful response$/
|
||||
*
|
||||
* @param integer $expected Expected response.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function checkStatus($expected = 200)
|
||||
{
|
||||
self::assertEquals(
|
||||
$expected,
|
||||
$this->apiConnection->getLastResponse()->getStatusCode()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^(?:|I )[Gg]ot response with content$/
|
||||
* @Then /^(?:|[Rr]esponse|[Ii]t's )[Cc]ontains$/
|
||||
*
|
||||
* @param PyStringNode $pattern Pattern for coduo/PHPMatcher.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function responseMatch(PyStringNode $pattern)
|
||||
{
|
||||
$error = '';
|
||||
|
||||
self::assertTrue(
|
||||
$this->match(
|
||||
$this->apiConnection->getLastResponseData(),
|
||||
$pattern->getRaw(),
|
||||
$error
|
||||
),
|
||||
$error
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^(?:|I )[Gg]ot empty response$/
|
||||
* @Then /^[Rr]esponse is empty$/
|
||||
* @Then /^[Ii]t's empty$/
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function emptyResponse()
|
||||
{
|
||||
$error = '';
|
||||
|
||||
self::assertTrue(
|
||||
PHPMatcher::match(
|
||||
$this->apiConnection->getLastResponseData(),
|
||||
'',
|
||||
$error
|
||||
),
|
||||
$error
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given /^(?:|I )[Aa]uthenticated as (?P<email>[\w@\.]+) with password (?P<password>.+)$/
|
||||
*
|
||||
* @param string $email User email.
|
||||
* @param string $password User password.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function authenticate($email, $password)
|
||||
{
|
||||
$this->apiConnection
|
||||
->request('POST', '/security/token/create', [
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
]);
|
||||
|
||||
self::assertEquals(
|
||||
200,
|
||||
$this->apiConnection->getLastResponse()->getStatusCode()
|
||||
);
|
||||
$response = json_decode(
|
||||
$this->apiConnection->getLastResponseData(),
|
||||
true
|
||||
);
|
||||
self::assertTrue(is_array($response), 'Invalid response from server');
|
||||
self::assertArrayHasKey('token', $response);
|
||||
$this->authToken = $response['token'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^(?:[Oo]ne|(?P<count>\d+)) [Ee]mail is sent$/
|
||||
*
|
||||
* @param integer $count Expected emails count.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function emailsCount($count = 1)
|
||||
{
|
||||
$mailer = $this->getMailCollector();
|
||||
|
||||
self::assertEquals($count, $mailer->getMessageCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^[Nn]o emails sent$/
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function noEmailsSent()
|
||||
{
|
||||
$mailer = $this->getMailCollector();
|
||||
|
||||
self::assertEquals(0, $mailer->getMessageCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^(?:|[Ff]irst )[Ee]mail subject is "(?P<subject>[^"]+)"$/
|
||||
* @Then /^(?P<index>\d+) email subject is "(?P<subject>[^"]+)"$/
|
||||
*
|
||||
* @param string $subject Expected email subject.
|
||||
* @param integer $index Email index.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function emailSubject($subject, $index = 0)
|
||||
{
|
||||
$mailer = $this->getMailCollector();
|
||||
|
||||
/** @var \Swift_Message $message */
|
||||
$message = $mailer->getMessages()[$index];
|
||||
self::assertEquals($subject, $message->getSubject());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface|\Symfony\Bundle\SwiftmailerBundle\DataCollector\MessageDataCollector
|
||||
*/
|
||||
protected function getMailCollector()
|
||||
{
|
||||
/** @var Profiler $profiler */
|
||||
$profiler = $this->container->get('profiler');
|
||||
$token = current($this->apiConnection->getLastResponse()
|
||||
->getHeader('X-Debug-Token'));
|
||||
|
||||
return $profiler->loadProfile($token)->getCollector('swiftmailer');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
|
||||
namespace Api\Util;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Cookie\CookieJar;
|
||||
use GuzzleHttp\Exception\BadResponseException;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
|
||||
/**
|
||||
* Class ApiConnection
|
||||
* Helper class. Handle all works with app api.
|
||||
*
|
||||
* @package Api\Util
|
||||
*/
|
||||
class ApiConnection
|
||||
{
|
||||
|
||||
/**
|
||||
* Used front controller.
|
||||
*/
|
||||
const FRONT_CONTROLLER = 'app_test.php';
|
||||
|
||||
/**
|
||||
* @var Client
|
||||
*/
|
||||
private $client;
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
private $debug;
|
||||
|
||||
/**
|
||||
* @var Response
|
||||
*/
|
||||
private $lastResponse;
|
||||
|
||||
/**
|
||||
* @var null|string
|
||||
*/
|
||||
private $lastResponseData;
|
||||
|
||||
/**
|
||||
* ApiConnection constructor.
|
||||
*
|
||||
* @param string $baseUrl Base url to app api.
|
||||
* @param boolean $debug Print debug information if set.
|
||||
*/
|
||||
public function __construct($baseUrl, $debug)
|
||||
{
|
||||
$cookies = false;
|
||||
|
||||
if ($debug) {
|
||||
$cookies = CookieJar::fromArray([
|
||||
'XDEBUG_SESSION' => 'PHPSTORM',
|
||||
], parse_url($baseUrl, PHP_URL_HOST));
|
||||
}
|
||||
|
||||
$this->client = new Client([
|
||||
'base_uri' => $baseUrl,
|
||||
'cookies' => $cookies,
|
||||
]);
|
||||
$this->debug = $debug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make request to api.
|
||||
*
|
||||
* @param string $method HTTP method name.
|
||||
* @param string $endpoint Relative to base url.
|
||||
* @param array $payload Request payload, may send as query string or as
|
||||
* json relative to method name.
|
||||
* @param string $authToken Application authentication token.
|
||||
*
|
||||
* @return mixed|\Psr\Http\Message\ResponseInterface
|
||||
*/
|
||||
public function request(
|
||||
$method,
|
||||
$endpoint,
|
||||
array $payload = [],
|
||||
$authToken = null
|
||||
) {
|
||||
$method = strtoupper($method);
|
||||
|
||||
// Prepare full endpoint url.
|
||||
$endpoint = self::FRONT_CONTROLLER . '/' . ltrim($endpoint, '/');
|
||||
|
||||
// Prepare request options.
|
||||
$options = [];
|
||||
// Add authorization token if it provided.
|
||||
if ($authToken !== null) {
|
||||
$options['headers'] = [ 'Authorization' => 'Bearer '. $authToken ];
|
||||
}
|
||||
|
||||
// Send payload as query string for 'GET' request.
|
||||
// For other methods, send in content as json.
|
||||
$options[($method === 'GET') ? 'query' : 'json'] = $payload;
|
||||
|
||||
// Show debug information about request.
|
||||
if ($this->debug) {
|
||||
echo 'Request: ' . $this->client->getConfig('base_uri')
|
||||
. $endpoint. PHP_EOL;
|
||||
echo 'Request options: ' . PHP_EOL
|
||||
. json_encode($options, JSON_PRETTY_PRINT)
|
||||
. PHP_EOL;
|
||||
}
|
||||
|
||||
try {
|
||||
// Make request to api and save response to class.
|
||||
$this->lastResponseData = null;
|
||||
$this->lastResponse =
|
||||
$this->client->request($method, $endpoint, $options);
|
||||
} catch (BadResponseException $e) {
|
||||
$this->lastResponse = $e->getResponse();
|
||||
}
|
||||
|
||||
if ($this->debug) {
|
||||
$decodedResponse = json_decode($this->getLastResponseData(), true);
|
||||
|
||||
if ($decodedResponse) {
|
||||
// In debug mode dump data received from server to output.
|
||||
print('Response data: ' . PHP_EOL
|
||||
. json_encode(
|
||||
$decodedResponse,
|
||||
JSON_PRETTY_PRINT
|
||||
). PHP_EOL
|
||||
);
|
||||
} else {
|
||||
print $this->getLastResponseData().PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->lastResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get last response.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function getLastResponse()
|
||||
{
|
||||
return $this->lastResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data from last response.
|
||||
*
|
||||
* @return null|string
|
||||
*/
|
||||
public function getLastResponseData()
|
||||
{
|
||||
if (($this->lastResponseData === null) && $this->lastResponse) {
|
||||
$this->lastResponseData = $this->lastResponse
|
||||
->getBody()
|
||||
->getContents();
|
||||
}
|
||||
|
||||
return $this->lastResponseData;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
Feature: Create authentication token
|
||||
As an anonymous user
|
||||
I should be able to obtain authentication token in order to make request
|
||||
to api
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I try to create token with proper data
|
||||
|
||||
Given I make POST request to /security/token/create
|
||||
"""
|
||||
{
|
||||
"email": "test@email.com",
|
||||
"password": "test"
|
||||
}
|
||||
"""
|
||||
And I got response with code 200
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"user": "@object@
|
||||
.entity('UserBundle:User', 'user, id, recipient, restrictions')
|
||||
.field('firstName', 'John')
|
||||
.field('lastName', 'Smith')
|
||||
",
|
||||
"token": "@string@",
|
||||
"refreshToken": "@string@"
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
Scenario Outline:
|
||||
I try to create token without providing any data .
|
||||
|
||||
Given I make POST request to /security/token/create
|
||||
"""
|
||||
{
|
||||
<payload>
|
||||
}
|
||||
"""
|
||||
And I got response with code 400
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": [
|
||||
"Credentials not provided."
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
Examples:
|
||||
| payload |
|
||||
| "email": "test@email.com" |
|
||||
| "password": "test" |
|
||||
| |
|
||||
|
||||
|
||||
@db-fixtures
|
||||
Scenario Outline:
|
||||
I try to create token with invalid data.
|
||||
|
||||
Given I make POST request to /security/token/create
|
||||
"""
|
||||
{
|
||||
"email": "<email>",
|
||||
"password": "<password>"
|
||||
}
|
||||
"""
|
||||
And I got response with code 401
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": [
|
||||
"Bad credentials."
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
Examples:
|
||||
| email | password |
|
||||
| test@email.com | invalid |
|
||||
| unknown@mail1.dev | test |
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
Feature: Refresh authentication token
|
||||
As an authenticated user
|
||||
I should be able to obtain authentication token by using my refresh token
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I try to refresh authentication token.
|
||||
|
||||
Given I make POST request to /security/token/refresh
|
||||
"""
|
||||
{
|
||||
"refreshToken": "user1_token"
|
||||
}
|
||||
"""
|
||||
And I got response with code 200
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"user": "@object@
|
||||
.entity('UserBundle:User', 'user, id, recipient, restrictions')
|
||||
.field('firstName', 'John')
|
||||
.field('lastName', 'Smith')
|
||||
",
|
||||
"token": "@string@",
|
||||
"refreshToken": "@string@"
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
Scenario:
|
||||
I try to refresh authentication token without refresh token provided.
|
||||
|
||||
Given I make POST request to /security/token/refresh
|
||||
"""
|
||||
{
|
||||
}
|
||||
"""
|
||||
And I got response with code 400
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": [
|
||||
"refreshToken: This value should not be null."
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I try to refresh authentication token by invalid refresh token.
|
||||
|
||||
Given I make POST request to /security/token/refresh
|
||||
"""
|
||||
{
|
||||
"refreshToken": "some token"
|
||||
}
|
||||
"""
|
||||
And I got response with code 401
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": [
|
||||
"Refresh token \"some token\" does not exist."
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
Feature: Create category
|
||||
As an authenticated user
|
||||
I should able to create new category
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I try to create new category.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/categories
|
||||
"""
|
||||
{
|
||||
"name": "new category",
|
||||
"parent": 4
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
@object@
|
||||
.entity('CacheBundle:Category', 'category, feed_tree, id')
|
||||
.field('name', 'new category')
|
||||
"""
|
||||
|
||||
Scenario:
|
||||
I try to create new category but not provide name.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/categories
|
||||
"""
|
||||
{
|
||||
}
|
||||
"""
|
||||
Then I got response with code 400
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"message": "This value should not be blank.",
|
||||
"transKey": "createCategoryNameEmpty",
|
||||
"type": "error",
|
||||
"parameters": {
|
||||
"current": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I try to create new category with already exists name.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/categories
|
||||
"""
|
||||
{
|
||||
"name": "My Content",
|
||||
"parent": 4
|
||||
}
|
||||
"""
|
||||
Then I got response with code 400
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"message": "User already have category with name \"My Content\".",
|
||||
"transKey": "createCategoryNameNotUnique",
|
||||
"type": "error",
|
||||
"parameters": {
|
||||
"current": "My Content"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
@@ -0,0 +1,75 @@
|
||||
Feature: Delete category
|
||||
As an authenticated user
|
||||
I should be able to delete my category
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I try to delete 'Test' category.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make DELETE request to /api/v1/categories/6
|
||||
Then I got response with code 204
|
||||
And it's empty
|
||||
And database don't has entity CacheBundle:Category
|
||||
| id | 6 |
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I try to delete 'Sub main sub 3' category which have subdirectories.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make DELETE request to /api/v1/categories/5
|
||||
Then I got response with code 204
|
||||
And it's empty
|
||||
And database don't has entity CacheBundle:Category
|
||||
| id | 5 |
|
||||
And don't has entity CacheBundle:Category
|
||||
| id | 6 |
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I try to delete category with unknown id.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make DELETE request to /api/v1/categories/1000
|
||||
Then I got response with code 404
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": [
|
||||
"Can't find category with id 1000."
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I try to delete category 'My Content' category.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make DELETE request to /api/v1/categories/1
|
||||
Then I got response with code 403
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": [
|
||||
"Can't delete internal category."
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I try to delete category for another user.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make DELETE request to /api/v1/categories/10
|
||||
Then I got response with code 403
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": [
|
||||
"Can't delete category owned by other user."
|
||||
]
|
||||
}
|
||||
"""
|
||||
@@ -0,0 +1,50 @@
|
||||
Feature: Get category
|
||||
As an authenticated user
|
||||
I should be able get information about specified category
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I try to get 'My Content' category information.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make GET request to /api/v1/categories/1
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
@object@
|
||||
.entity('CacheBundle:Category', 'category, feed_tree, id')
|
||||
.field('id', 1)
|
||||
.field('name', 'My Content')
|
||||
"""
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I try to get category information by unknown id.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make GET request to /api/v1/categories/1000
|
||||
Then I got response with code 404
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": [
|
||||
"Can't find Category with id 1000."
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I try to get category owned by other user.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make GET request to /api/v1/categories/9
|
||||
Then I got response with code 403
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": [
|
||||
"Can't read category owned by other user."
|
||||
]
|
||||
}
|
||||
"""
|
||||
@@ -0,0 +1,25 @@
|
||||
Feature: Get list of categories
|
||||
As an authenticated
|
||||
I should be able to get list of my categories
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I try to get list of categories.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make GET request to /api/v1/categories
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"data": "@array@
|
||||
.every(entity('CacheBundle:Category', 'category_tree, feed_tree, id'))
|
||||
.one(field('name', 'My Content'))
|
||||
.one(field('name', 'Deleted Content'))
|
||||
",
|
||||
"count": "@integer@.greaterThan(1)",
|
||||
"totalCount": "@integer@.greaterThan(1)",
|
||||
"page": 1,
|
||||
"limit": "@integer@.greaterThan(1)"
|
||||
}
|
||||
"""
|
||||
@@ -0,0 +1,154 @@
|
||||
Feature: Move category
|
||||
As an authenticated user
|
||||
I should be able to move my category from one place to another
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I try to move 'Sub main sub 3' category to another category.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/categories/5/move_to/4
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"data": "@array@
|
||||
.every(entity('CacheBundle:Category', 'category_tree, feed_tree, id'))
|
||||
.one(
|
||||
field('name', 'My Content'),
|
||||
field('childes',
|
||||
one(
|
||||
field('id', 2),
|
||||
field('childes',
|
||||
one(
|
||||
field('id', 4),
|
||||
field('childes', one(
|
||||
field('id', 5),
|
||||
field('childes', one(field('id', 6)))
|
||||
))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
",
|
||||
"count": "@integer@.greaterThan(1)",
|
||||
"totalCount": "@integer@.greaterThan(1)",
|
||||
"page": 1,
|
||||
"limit": "@integer@.greaterThan(1)"
|
||||
}
|
||||
"""
|
||||
And database has entity CacheBundle:Category
|
||||
| id | 5 |
|
||||
| name | Sub main sub 3 |
|
||||
| user | 1 |
|
||||
| parent | 4 |
|
||||
And database has entity CacheBundle:Category
|
||||
| id | 6 |
|
||||
| name | Test |
|
||||
| user | 1 |
|
||||
| parent | 5 |
|
||||
|
||||
Scenario:
|
||||
I try to move unknown category.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/categories/1000/move_to/7
|
||||
Then I got response with code 404
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": [
|
||||
"Can't find category with id 1000."
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
Scenario:
|
||||
I try to move my category into unknown.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/categories/5/move_to/1000
|
||||
Then I got response with code 404
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": [
|
||||
"Can't find category with id 1000."
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I try to move 'My Content' category which is internal.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/categories/1/move_to/7
|
||||
Then I got response with code 403
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": [
|
||||
"Can't move internal category."
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I try to move another user category.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/categories/10/move_to/1
|
||||
Then I got response with code 404
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": [
|
||||
"Can't find category with id 10."
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I try to move 'Sub main sub 3' category inside it self.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/categories/5/move_to/5
|
||||
Then I got response with code 400
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": [
|
||||
"Try to place category inside itself."
|
||||
]
|
||||
}
|
||||
"""
|
||||
And database has entity CacheBundle:Category
|
||||
| id | 5 |
|
||||
| name | Sub main sub 3 |
|
||||
| user | 1 |
|
||||
| parent | 2 |
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I try to move 'Sub main sub 3' category inside one of child.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/categories/5/move_to/6
|
||||
Then I got response with code 400
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": [
|
||||
"Try to place category inside it child."
|
||||
]
|
||||
}
|
||||
"""
|
||||
And database has entity CacheBundle:Category
|
||||
| id | 5 |
|
||||
| name | Sub main sub 3 |
|
||||
| user | 1 |
|
||||
| parent | 2 |
|
||||
@@ -0,0 +1,274 @@
|
||||
Feature: Update category
|
||||
As an authenticated user
|
||||
I should be able to update any available properties of my category
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I try to rename 'Test' category.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make PUT request to /api/v1/categories/6
|
||||
"""
|
||||
{
|
||||
"name": "Awesome Category",
|
||||
"parent": 5
|
||||
}
|
||||
"""
|
||||
Then I got response with code 200
|
||||
And it's contains
|
||||
"""
|
||||
@object@
|
||||
.entity('CacheBundle:Category', 'category, feed_tree, id')
|
||||
.field('id', 6)
|
||||
.field('name', 'Awesome Category')
|
||||
"""
|
||||
And database has entity CacheBundle:Category
|
||||
| id | 6 |
|
||||
| name | Awesome Category |
|
||||
| user | 1 |
|
||||
| parent | 5 |
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I try to move 'Test' category to another category.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make PUT request to /api/v1/categories/6
|
||||
"""
|
||||
{
|
||||
"name": "Test",
|
||||
"parent": 4
|
||||
}
|
||||
"""
|
||||
Then I got response with code 200
|
||||
And it's contains
|
||||
"""
|
||||
@object@
|
||||
.entity('CacheBundle:Category', 'category, feed_tree, id')
|
||||
.field('id', 6)
|
||||
.field('name', 'Test')
|
||||
"""
|
||||
And database has entity CacheBundle:Category
|
||||
| id | 6 |
|
||||
| name | Test |
|
||||
| user | 1 |
|
||||
| parent | 4 |
|
||||
|
||||
Scenario:
|
||||
I try to update 'Test' but not provide necessary information.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make PUT request to /api/v1/categories/6
|
||||
"""
|
||||
{
|
||||
}
|
||||
"""
|
||||
Then I got response with code 400
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"message": "This value should not be blank.",
|
||||
"transKey": "updateCategoryNameEmpty",
|
||||
"type": "error",
|
||||
"parameters": {
|
||||
"current": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I try to rename category and set already exists name
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make PUT request to /api/v1/categories/6
|
||||
"""
|
||||
{
|
||||
"name": "My Content",
|
||||
"parent": 5
|
||||
}
|
||||
"""
|
||||
Then I got response with code 400
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"message": "User already have category with name \"My Content\".",
|
||||
"transKey": "updateCategoryNameNotUnique",
|
||||
"type": "error",
|
||||
"parameters": {
|
||||
"current": "My Content"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I try to update category with unknown id.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make PUT request to /api/v1/categories/1000
|
||||
"""
|
||||
{
|
||||
"name": "Awesome Category",
|
||||
"parent": 5
|
||||
}
|
||||
"""
|
||||
Then I got response with code 404
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": [
|
||||
"Can't find Category with id 1000."
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I try to update 'My Content' category which is internal.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make PUT request to /api/v1/categories/1
|
||||
"""
|
||||
{
|
||||
"name": "Awesome category"
|
||||
}
|
||||
"""
|
||||
Then I got response with code 403
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": [
|
||||
"Can't update internal category."
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I try to update category for another user.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make PUT request to /api/v1/categories/10
|
||||
"""
|
||||
{
|
||||
"name": "Awesome category",
|
||||
"parent": 4
|
||||
}
|
||||
"""
|
||||
Then I got response with code 403
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": [
|
||||
"Can't update category owned by other user."
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I try to move 'Test' category inside it self.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make PUT request to /api/v1/categories/6
|
||||
"""
|
||||
{
|
||||
"name": "Test",
|
||||
"parent": 6
|
||||
}
|
||||
"""
|
||||
Then I got response with code 400
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"message": "Try to place category inside itself.",
|
||||
"transKey": "updateCategoryParent",
|
||||
"type": "error",
|
||||
"parameters": []
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
And database has entity CacheBundle:Category
|
||||
| id | 6 |
|
||||
| name | Test |
|
||||
| user | 1 |
|
||||
| parent | 5 |
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I try to move 'Test' category inside unknown category.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make PUT request to /api/v1/categories/6
|
||||
"""
|
||||
{
|
||||
"name": "Test",
|
||||
"parent": 1000
|
||||
}
|
||||
"""
|
||||
Then I got response with code 400
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"message": "This value is not valid.",
|
||||
"transKey": "updateCategoryParentInvalid",
|
||||
"type": "error",
|
||||
"parameters": {
|
||||
"current": "1000",
|
||||
"available": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
And database has entity CacheBundle:Category
|
||||
| id | 6 |
|
||||
| name | Test |
|
||||
| user | 1 |
|
||||
| parent | 5 |
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I try to move 'Sub main sub 3' category inside one of child.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make PUT request to /api/v1/categories/5
|
||||
"""
|
||||
{
|
||||
"name": "Sub main sub 3",
|
||||
"parent": 6
|
||||
}
|
||||
"""
|
||||
Then I got response with code 400
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"message": "Try to place category inside it child.",
|
||||
"transKey": "updateCategoryParent",
|
||||
"type": "error",
|
||||
"parameters": []
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
And database has entity CacheBundle:Category
|
||||
| id | 5 |
|
||||
| name | Sub main sub 3 |
|
||||
| user | 1 |
|
||||
| parent | 2 |
|
||||
@@ -0,0 +1,266 @@
|
||||
Feature: Create simple notification
|
||||
As an authenticated user
|
||||
I should be able to create new simple notification
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I try to create new simple notification.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/notifications
|
||||
"""
|
||||
{
|
||||
"name": "New notification",
|
||||
"recipients": [ 1 ],
|
||||
"notificationType": "alert",
|
||||
"themeType": "plain",
|
||||
"theme": 1,
|
||||
"subject": "Some subject",
|
||||
"published": true,
|
||||
"automatedSubject": false,
|
||||
"allowUnsubscribe": true,
|
||||
"unsubscribeNotification": true,
|
||||
"sources": [
|
||||
{
|
||||
"type": "feed",
|
||||
"id": 1
|
||||
}
|
||||
],
|
||||
"sendWhenEmpty": false,
|
||||
"timezone": "Asia/Novosibirsk",
|
||||
"automatic": [
|
||||
{
|
||||
"type": "daily",
|
||||
"time": "15m",
|
||||
"days": "all"
|
||||
},
|
||||
{
|
||||
"type": "weekly",
|
||||
"period": "third",
|
||||
"day": "monday",
|
||||
"hour": 11,
|
||||
"minute": 45
|
||||
},
|
||||
{
|
||||
"type": "monthly",
|
||||
"day": 3,
|
||||
"hour": 11,
|
||||
"minute": 0
|
||||
},
|
||||
{
|
||||
"type": "monthly",
|
||||
"day": "last",
|
||||
"hour": 0,
|
||||
"minute": 55
|
||||
}
|
||||
],
|
||||
"sendUntil": "2017-10-01",
|
||||
"plainDiff": {},
|
||||
"enhancedDiff": {}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
@object@
|
||||
.entity('UserBundle:Notification', 'notification, schedule, id')
|
||||
.field('type', 'alert')
|
||||
.field('name', 'New notification')
|
||||
.field('subject', 'Some subject')
|
||||
.field('owner', field('id', 1))
|
||||
.field('sources',
|
||||
count(1),
|
||||
one(field('type', 'feed'), field('id', 1), field('name', 'test1'))
|
||||
)
|
||||
"""
|
||||
And database has entity UserBundle:Notification\Notification
|
||||
| name | New notification |
|
||||
| notificationType | alert |
|
||||
| owner | 1 |
|
||||
| subject | Some subject |
|
||||
| automatedSubject | false |
|
||||
| published | true |
|
||||
| allowUnsubscribe | true |
|
||||
| unsubscribeNotification | true |
|
||||
| sendWhenEmpty | false |
|
||||
| timezone | Asia/Novosibirsk |
|
||||
| sendUntil | 2017-10-01 |
|
||||
| active | true |
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I try to create new simple notification without sources, recipients and scheduling.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/notifications
|
||||
"""
|
||||
{
|
||||
"name": "New notification",
|
||||
"recipients": [],
|
||||
"notificationType": "alert",
|
||||
"themeType": "plain",
|
||||
"theme": 1,
|
||||
"subject": "Some subject",
|
||||
"published": true,
|
||||
"automatedSubject": false,
|
||||
"allowUnsubscribe": true,
|
||||
"unsubscribeNotification": true,
|
||||
"sources": [],
|
||||
"sendWhenEmpty": false,
|
||||
"timezone": "Asia/Novosibirsk",
|
||||
"automatic": [],
|
||||
"sendUntil": "2017-10-01",
|
||||
"plainDiff": {},
|
||||
"enhancedDiff": {}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
@object@
|
||||
.entity('UserBundle:Notification', 'notification, schedule, id')
|
||||
.field('type', 'alert')
|
||||
.field('name', 'New notification')
|
||||
.field('subject', 'Some subject')
|
||||
.field('owner', field('id', 1))
|
||||
.field('recipients', count(0))
|
||||
.field('sources', count(0))
|
||||
.field('automatic', count(0))
|
||||
"""
|
||||
And database has entity UserBundle:Notification\Notification
|
||||
| name | New notification |
|
||||
| notificationType | alert |
|
||||
| owner | 1 |
|
||||
| subject | Some subject |
|
||||
| automatedSubject | false |
|
||||
| published | true |
|
||||
| allowUnsubscribe | true |
|
||||
| unsubscribeNotification | true |
|
||||
| sendWhenEmpty | false |
|
||||
| timezone | Asia/Novosibirsk |
|
||||
| sendUntil | 2017-10-01 |
|
||||
| active | true |
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I try to create new simple notification with invalid recipient.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/notifications
|
||||
"""
|
||||
{
|
||||
"name": "New notification",
|
||||
"recipients": [ 1000 ],
|
||||
"notificationType": "alert",
|
||||
"themeType": "plain",
|
||||
"theme": 1,
|
||||
"subject": "Some subject",
|
||||
"published": true,
|
||||
"automatedSubject": false,
|
||||
"allowUnsubscribe": true,
|
||||
"unsubscribeNotification": true,
|
||||
"sources": [],
|
||||
"sendWhenEmpty": false,
|
||||
"timezone": "Asia/Novosibirsk",
|
||||
"automatic": [],
|
||||
"sendUntil": "2017-10-01",
|
||||
"plainDiff": {},
|
||||
"enhancedDiff": {}
|
||||
}
|
||||
"""
|
||||
Then I got response with code 400
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"message": "This value is not valid.",
|
||||
"transKey": "createNotificationRecipientsInvalid",
|
||||
"type": "error",
|
||||
"parameters": {
|
||||
"current": [ 1000 ],
|
||||
"available": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
And database don't has entity UserBundle:Notification\Notification
|
||||
| name | New notification |
|
||||
| notificationType | alert |
|
||||
| owner | 1 |
|
||||
| subject | Some subject |
|
||||
| automatedSubject | false |
|
||||
| published | true |
|
||||
| allowUnsubscribe | true |
|
||||
| unsubscribeNotification | true |
|
||||
| sendWhenEmpty | false |
|
||||
| timezone | Asia/Novosibirsk |
|
||||
| sendUntil | 2017-10-01 |
|
||||
| active | true |
|
||||
|
||||
@db-fixtures
|
||||
Scenario Outline:
|
||||
I try to create new simple notification with invalid source.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/notifications
|
||||
"""
|
||||
{
|
||||
"name": "New notification",
|
||||
"recipients": [],
|
||||
"notificationType": "alert",
|
||||
"themeType": "plain",
|
||||
"theme": 1,
|
||||
"subject": "Some subject",
|
||||
"published": true,
|
||||
"automatedSubject": false,
|
||||
"allowUnsubscribe": true,
|
||||
"unsubscribeNotification": true,
|
||||
"sources": [
|
||||
{
|
||||
<payload>
|
||||
}
|
||||
],
|
||||
"sendWhenEmpty": false,
|
||||
"timezone": "Asia/Novosibirsk",
|
||||
"automatic": [],
|
||||
"sendUntil": "2017-10-01",
|
||||
"plainDiff": {},
|
||||
"enhancedDiff": {}
|
||||
}
|
||||
"""
|
||||
Then I got response with code 400
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": "@array@
|
||||
.one(
|
||||
field('message', '<message>'),
|
||||
field('transKey', '<transKey>'),
|
||||
field('type', 'error')
|
||||
)
|
||||
"
|
||||
}
|
||||
"""
|
||||
And database don't has entity UserBundle:Notification\Notification
|
||||
| name | New notification |
|
||||
| notificationType | alert |
|
||||
| owner | 1 |
|
||||
| subject | Some subject |
|
||||
| automatedSubject | false |
|
||||
| published | true |
|
||||
| allowUnsubscribe | true |
|
||||
| unsubscribeNotification | true |
|
||||
| sendWhenEmpty | false |
|
||||
| timezone | Asia/Novosibirsk |
|
||||
| sendUntil | 2017-10-01 |
|
||||
| active | true |
|
||||
|
||||
Examples:
|
||||
| payload | message | transKey |
|
||||
| | Some of sources has invalid id. | createNotificationSources |
|
||||
| "type": "feed" | This value should not be blank. | createNotificationSourcesIdEmpty |
|
||||
| "id": 1 | This value should not be blank. | createNotificationSourcesTypeEmpty |
|
||||
| "type": "some", "id": 1 | This value is not valid. | createNotificationSourcesTypeInvalid |
|
||||
| "type": "feed", "id": 1000 | Some of sources has invalid id. | createNotificationSources |
|
||||
@@ -0,0 +1,58 @@
|
||||
Feature: Get list of notifications
|
||||
As an authenticated user
|
||||
I should be able to get list of my notifications
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I get list of my notification's.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make GET request to /api/v1/notifications
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"notifications": {
|
||||
"data": "@array@.every(
|
||||
entity('UserBundle:Notification', 'notification_list, schedule, id'),
|
||||
field('owner', field('id', 1))
|
||||
)",
|
||||
"count": "@integer@",
|
||||
"totalCount": "@integer@",
|
||||
"page": "@integer@",
|
||||
"limit": "@integer@"
|
||||
},
|
||||
"meta": {
|
||||
"sort": {
|
||||
"field": "name",
|
||||
"direction": "asc"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
When I make GET request to /api/v1/notifications
|
||||
| onlyPublished | true |
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"notifications": {
|
||||
"data": "@array@.every(
|
||||
entity('UserBundle:Notification', 'notification_list, schedule, id'),
|
||||
field('owner', field('id', 1)),
|
||||
field('published', true)
|
||||
)",
|
||||
"count": "@integer@",
|
||||
"totalCount": "@integer@",
|
||||
"page": "@integer@",
|
||||
"limit": "@integer@"
|
||||
},
|
||||
"meta": {
|
||||
"sort": {
|
||||
"field": "name",
|
||||
"direction": "asc"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
@@ -0,0 +1,188 @@
|
||||
Feature: Use 'Article Date' advanced filter
|
||||
As an authenticated user
|
||||
I should be able to use 'Article Date' for filtering search results
|
||||
|
||||
@external-index-fixtures @db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' which appears in documents from US and use `Article Date`
|
||||
advanced filters with value '31 Days'.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"filters": {
|
||||
"country": {
|
||||
"include": [ "US" ]
|
||||
}
|
||||
},
|
||||
"advancedFilters": {
|
||||
"articleDate": "31 Days"
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id'),
|
||||
field('source', field('country', 'US')),
|
||||
field('published', gte('#now().modify(\"- 31 Days\").format(\"c\")#'))
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {
|
||||
"country": {
|
||||
"include": [ "US" ]
|
||||
}
|
||||
},
|
||||
"advancedFilters": {
|
||||
"articleDate": "31 Days"
|
||||
}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
And database has 1 entity CacheBundle:Query\SimpleQuery
|
||||
| id | 4 |
|
||||
| raw | cat |
|
||||
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"filters": {
|
||||
"country": {
|
||||
"include": [ "US" ]
|
||||
}
|
||||
},
|
||||
"advancedFilters": {
|
||||
"articleDate": "31 Days"
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id'),
|
||||
field('source', field('country', 'US')),
|
||||
field('published', gte('#now().modify(\"- 31 Days\").format(\"c\")#'))
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {
|
||||
"country": {
|
||||
"include": [ "US" ]
|
||||
}
|
||||
},
|
||||
"advancedFilters": {
|
||||
"articleDate": "31 Days"
|
||||
}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
And database has 1 entity CacheBundle:Query\SimpleQuery
|
||||
| id | 4 |
|
||||
| raw | cat |
|
||||
And database don't has entity CacheBundle:Query\SimpleQuery
|
||||
| id | 5 |
|
||||
| raw | cat |
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' which appears in documents from US and also use `Article Date`
|
||||
advanced filters with empty value.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"filters": {
|
||||
"country": {
|
||||
"include": [ "US" ]
|
||||
}
|
||||
},
|
||||
"advancedFilters": {
|
||||
"articleDate": ""
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got response with code 400
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": "@array@"
|
||||
}
|
||||
"""
|
||||
And database don't has entity CacheBundle:Query\SimpleQuery
|
||||
| id | 4 |
|
||||
| raw | cat |
|
||||
|
||||
@external-index-fixtures @db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' which appears in documents from US and use `Article Date`
|
||||
advanced filters with invalid value.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"filters": {
|
||||
"country": {
|
||||
"include": [ "US" ]
|
||||
}
|
||||
},
|
||||
"advancedFilters": {
|
||||
"articleDate": "111"
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got response with code 400
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": "@array@"
|
||||
}
|
||||
"""
|
||||
And database don't has entity CacheBundle:Query\SimpleQuery
|
||||
| id | 4 |
|
||||
| raw | cat |
|
||||
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
Feature: Use 'Article Language' advanced filter
|
||||
As an authenticated user
|
||||
I should be able to use 'Article Language' for filtering search results
|
||||
|
||||
@external-index-fixtures @db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' and use `Article Language` advanced filters with value
|
||||
'en'.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"advancedFilters": {
|
||||
"articleLanguage": "en"
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id'),
|
||||
field('language', 'en')
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {},
|
||||
"advancedFilters": {
|
||||
"articleLanguage": "en"
|
||||
}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
And database has 1 entity CacheBundle:Query\SimpleQuery
|
||||
| id | 4 |
|
||||
| raw | cat |
|
||||
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"advancedFilters": {
|
||||
"articleLanguage": "en"
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id'),
|
||||
field('language', 'en')
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {},
|
||||
"advancedFilters": {
|
||||
"articleLanguage": "en"
|
||||
}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
And database has 1 entity CacheBundle:Query\SimpleQuery
|
||||
| id | 4 |
|
||||
| raw | cat |
|
||||
And database don't has entity CacheBundle:Query\SimpleQuery
|
||||
| id | 5 |
|
||||
| raw | cat |
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' and use `Article Language` advanced filters with empty
|
||||
value.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"advancedFilters": {
|
||||
"articleLanguage": ""
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(entity('CacheBundle:Document', 'document, id'))",
|
||||
"count": "@integer@",
|
||||
"totalCount": "@integer@",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {},
|
||||
"advancedFilters": {
|
||||
"articleLanguage": ""
|
||||
}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
And database has entity CacheBundle:Query\SimpleQuery
|
||||
| id | 4 |
|
||||
| raw | cat |
|
||||
|
||||
@external-index-fixtures @db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' and use `Article Language` advanced filters with invalid
|
||||
value.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"advancedFilters": {
|
||||
"articleLanguage": "some"
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": [],
|
||||
"count": 0,
|
||||
"totalCount": 0,
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {},
|
||||
"advancedFilters": {
|
||||
"articleLanguage": "some"
|
||||
}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
And database has entity CacheBundle:Query\SimpleQuery
|
||||
| id | 4 |
|
||||
| raw | cat |
|
||||
@@ -0,0 +1,240 @@
|
||||
Feature: Use 'Author' advanced filter
|
||||
As an authenticated user
|
||||
I should be able to use 'Author' for filtering search results
|
||||
|
||||
@external-index-fixtures @db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' which appears in documents from US and use `Author`
|
||||
advanced filters with value 'Gracie Pfeffer'.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"filters": {
|
||||
"country": {
|
||||
"include": [ "US" ]
|
||||
}
|
||||
},
|
||||
"advancedFilters": {
|
||||
"author": "Gracie Pfeffer"
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id'),
|
||||
field('source', field('country', 'US')),
|
||||
field('author', field('name', 'Gracie Pfeffer'))
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {
|
||||
"country": {
|
||||
"include": [ "US" ]
|
||||
}
|
||||
},
|
||||
"advancedFilters": {
|
||||
"author": "Gracie Pfeffer"
|
||||
}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
And database has 1 entity CacheBundle:Query\SimpleQuery
|
||||
| id | 4 |
|
||||
| raw | cat |
|
||||
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"filters": {
|
||||
"country": {
|
||||
"include": [ "US" ]
|
||||
}
|
||||
},
|
||||
"advancedFilters": {
|
||||
"author": "Gracie Pfeffer"
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id'),
|
||||
field('source', field('country', 'US')),
|
||||
field('author', field('name', 'Gracie Pfeffer'))
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {
|
||||
"country": {
|
||||
"include": [ "US" ]
|
||||
}
|
||||
},
|
||||
"advancedFilters": {
|
||||
"author": "Gracie Pfeffer"
|
||||
}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
And database has 1 entity CacheBundle:Query\SimpleQuery
|
||||
| id | 4 |
|
||||
| raw | cat |
|
||||
And database don't has entity CacheBundle:Query\SimpleQuery
|
||||
| id | 5 |
|
||||
| raw | cat |
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' which appears in documents from US and use `Author`
|
||||
advanced filters with empty value.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"filters": {
|
||||
"country": {
|
||||
"include": [ "US" ]
|
||||
}
|
||||
},
|
||||
"advancedFilters": {
|
||||
"author": ""
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id'),
|
||||
field('source', field('country', 'US'))
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {
|
||||
"country": {
|
||||
"include": [ "US" ]
|
||||
}
|
||||
},
|
||||
"advancedFilters": {
|
||||
"author": ""
|
||||
}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
And database has entity CacheBundle:Query\SimpleQuery
|
||||
| id | 4 |
|
||||
| raw | cat |
|
||||
|
||||
@external-index-fixtures @db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' which appears in documents from US and use `Author`
|
||||
advanced filters with invalid value.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"filters": {
|
||||
"country": {
|
||||
"include": [ "US" ]
|
||||
}
|
||||
},
|
||||
"advancedFilters": {
|
||||
"author": "some"
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": [],
|
||||
"count": 0,
|
||||
"totalCount": 0,
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {
|
||||
"country": {
|
||||
"include": [ "US" ]
|
||||
}
|
||||
},
|
||||
"advancedFilters": {
|
||||
"author": "some"
|
||||
}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
And database has entity CacheBundle:Query\SimpleQuery
|
||||
| id | 4 |
|
||||
| raw | cat |
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
Feature: Use 'Publisher' advanced filter
|
||||
As an authenticated user
|
||||
I should be able to use 'Publisher' for filtering search results
|
||||
|
||||
@external-index-fixtures @db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' and use `Publisher` advanced filters with value 'msnbc'.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"advancedFilters": {
|
||||
"publisher": "msnbc"
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id'),
|
||||
field('publisher', contains('msnbc', true))
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {},
|
||||
"advancedFilters": {
|
||||
"publisher": "msnbc"
|
||||
}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
And database has 1 entity CacheBundle:Query\SimpleQuery
|
||||
| id | 4 |
|
||||
| raw | cat |
|
||||
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"advancedFilters": {
|
||||
"publisher": "msnbc"
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id'),
|
||||
field('publisher', contains('msnbc', true))
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {},
|
||||
"advancedFilters": {
|
||||
"publisher": "msnbc"
|
||||
}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
And database has 1 entity CacheBundle:Query\SimpleQuery
|
||||
| id | 4 |
|
||||
| raw | cat |
|
||||
And database don't has entity CacheBundle:Query\SimpleQuery
|
||||
| id | 5 |
|
||||
| raw | cat |
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' and use `Publisher` advanced filters with empty value.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"advancedFilters": {
|
||||
"publisher": ""
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id')
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {},
|
||||
"advancedFilters": {
|
||||
"publisher": ""
|
||||
}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
And database has entity CacheBundle:Query\SimpleQuery
|
||||
| id | 4 |
|
||||
| raw | cat |
|
||||
|
||||
@external-index-fixtures @db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' and use `Publisher` advanced filters with invalid value.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"advancedFilters": {
|
||||
"publisher": "some"
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": [],
|
||||
"count": 0,
|
||||
"totalCount": 0,
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {},
|
||||
"advancedFilters": {
|
||||
"publisher": "some"
|
||||
}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
And database has entity CacheBundle:Query\SimpleQuery
|
||||
| id | 4 |
|
||||
| raw | cat |
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
Feature: Use 'Reach' advanced filter
|
||||
As an authenticated user
|
||||
I should be able to use 'Reach' for filtering search results
|
||||
|
||||
@external-index-fixtures @db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' and use `Reach` advanced filters with value '10000+'.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"advancedFilters": {
|
||||
"reach": "10000+"
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id'),
|
||||
field('views', greaterThan(10000), lowerThan(25000))
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {},
|
||||
"advancedFilters": {
|
||||
"reach": "10000+"
|
||||
}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
And database has 1 entity CacheBundle:Query\SimpleQuery
|
||||
| id | 4 |
|
||||
| raw | cat |
|
||||
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"advancedFilters": {
|
||||
"reach": "10000+"
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id'),
|
||||
field('views', greaterThan(10000), lowerThan(25000))
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {},
|
||||
"advancedFilters": {
|
||||
"reach": "10000+"
|
||||
}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
And database has 1 entity CacheBundle:Query\SimpleQuery
|
||||
| id | 4 |
|
||||
| raw | cat |
|
||||
And database don't has entity CacheBundle:Query\SimpleQuery
|
||||
| id | 5 |
|
||||
| raw | cat |
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' and use `Reach` advanced filters with empty value.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"advancedFilters": {
|
||||
"reach": ""
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got response with code 400
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": "@array@"
|
||||
}
|
||||
"""
|
||||
And database don't has entity CacheBundle:Query\SimpleQuery
|
||||
| id | 4 |
|
||||
| raw | cat |
|
||||
|
||||
@external-index-fixtures @db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' and use `Reach` advanced filters with invalid value.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"advancedFilters": {
|
||||
"reach": "111"
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got response with code 400
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": "@array@"
|
||||
}
|
||||
"""
|
||||
And database don't has entity CacheBundle:Query\SimpleQuery
|
||||
| id | 3 |
|
||||
| raw | cat |
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
Feature: Use 'Source' advanced filter
|
||||
As an authenticated user
|
||||
I should be able to use 'Source' for filtering search results
|
||||
|
||||
@external-index-fixtures @db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' which appears in documents from US and after it add `source`
|
||||
advanced filters with value 'CNN'.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"filters": {
|
||||
"country": {
|
||||
"include": [ "US" ]
|
||||
}
|
||||
},
|
||||
"advancedFilters": {
|
||||
"source": "CNN"
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id'),
|
||||
field('source',
|
||||
field('country', 'US'),
|
||||
field('title', 'CNN')
|
||||
)
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {
|
||||
"country": {
|
||||
"include": [ "US" ]
|
||||
}
|
||||
},
|
||||
"advancedFilters": {
|
||||
"source": "CNN"
|
||||
}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
And database has 1 entity CacheBundle:Query\SimpleQuery
|
||||
| id | 4 |
|
||||
| raw | cat |
|
||||
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"filters": {
|
||||
"country": {
|
||||
"include": [ "US" ]
|
||||
}
|
||||
},
|
||||
"advancedFilters": {
|
||||
"source": "CNN"
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id'),
|
||||
field('source',
|
||||
field('country', 'US'),
|
||||
field('title', 'CNN')
|
||||
)
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {
|
||||
"country": {
|
||||
"include": [ "US" ]
|
||||
}
|
||||
},
|
||||
"advancedFilters": {
|
||||
"source": "CNN"
|
||||
}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
And database has 1 entity CacheBundle:Query\SimpleQuery
|
||||
| id | 4 |
|
||||
| raw | cat |
|
||||
And database don't has entity CacheBundle:Query\SimpleQuery
|
||||
| id | 5 |
|
||||
| raw | cat |
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' which appears in documents from US and after it add `source`
|
||||
advanced filters with empty value.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"filters": {
|
||||
"country": {
|
||||
"include": [ "US" ]
|
||||
}
|
||||
},
|
||||
"advancedFilters": {
|
||||
"source": ""
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id'),
|
||||
field('source',
|
||||
field('country', 'US')
|
||||
)
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {
|
||||
"country": {
|
||||
"include": [ "US" ]
|
||||
}
|
||||
},
|
||||
"advancedFilters": {
|
||||
"source": ""
|
||||
}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
And database has entity CacheBundle:Query\SimpleQuery
|
||||
| id | 4 |
|
||||
| raw | cat |
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' and use `Source` advanced filters with unknown value.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"filters": {
|
||||
"country": {
|
||||
"include": [ "US" ]
|
||||
}
|
||||
},
|
||||
"advancedFilters": {
|
||||
"source": "some"
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": [],
|
||||
"count": 0,
|
||||
"totalCount": 0,
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {
|
||||
"country": {
|
||||
"include": [ "US" ]
|
||||
}
|
||||
},
|
||||
"advancedFilters": {
|
||||
"source": "some"
|
||||
}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
And database has entity CacheBundle:Query\SimpleQuery
|
||||
| id | 4 |
|
||||
| raw | cat |
|
||||
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
Feature: Use 'Source City' advanced filter
|
||||
As an authenticated user
|
||||
I should be able to use 'Source City' for filtering search results
|
||||
|
||||
@external-index-fixtures @db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' and use `Source City` advanced filters with value 'Arizona'.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"advancedFilters": {
|
||||
"sourceCity": "Amazing City"
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id'),
|
||||
field('source', field('city', 'Amazing City'))
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {},
|
||||
"advancedFilters": {
|
||||
"sourceCity": "Amazing City"
|
||||
}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
And database has 1 entity CacheBundle:Query\SimpleQuery
|
||||
| id | 4 |
|
||||
| raw | cat |
|
||||
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"advancedFilters": {
|
||||
"sourceCity": "Amazing City"
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id'),
|
||||
field('source', field('city', 'Amazing City'))
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {},
|
||||
"advancedFilters": {
|
||||
"sourceCity": "Amazing City"
|
||||
}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
And database has 1 entity CacheBundle:Query\SimpleQuery
|
||||
| id | 4 |
|
||||
| raw | cat |
|
||||
And database don't has entity CacheBundle:Query\SimpleQuery
|
||||
| id | 5 |
|
||||
| raw | cat |
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' and use `Source City` advanced filters with empty value.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"advancedFilters": {
|
||||
"sourceCity": ""
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id')
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {},
|
||||
"advancedFilters": {
|
||||
"sourceCity": ""
|
||||
}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
And database has entity CacheBundle:Query\SimpleQuery
|
||||
| id | 4 |
|
||||
| raw | cat |
|
||||
|
||||
@external-index-fixtures @db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' and after it add `sourceCity` advanced filters with invalid
|
||||
value.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"advancedFilters": {
|
||||
"sourceCity": "some"
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": [],
|
||||
"count": 0,
|
||||
"totalCount": 0,
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {},
|
||||
"advancedFilters": {
|
||||
"sourceCity": "some"
|
||||
}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
And database has 1 entity CacheBundle:Query\SimpleQuery
|
||||
| id | 4 |
|
||||
| raw | cat |
|
||||
@@ -0,0 +1,197 @@
|
||||
Feature: Use 'Source Country' advanced filter
|
||||
As an authenticated user
|
||||
I should be able to use 'Source Country' for filtering search results
|
||||
|
||||
@external-index-fixtures @db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' and use `Source Country` advanced filters with value 'US'.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"advancedFilters": {
|
||||
"sourceCountry": "US"
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id'),
|
||||
field('source', field('country', 'US'))
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {},
|
||||
"advancedFilters": {
|
||||
"sourceCountry": "US"
|
||||
}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
And database has 1 entity CacheBundle:Query\SimpleQuery
|
||||
| id | 4 |
|
||||
| raw | cat |
|
||||
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"advancedFilters": {
|
||||
"sourceCountry": "US"
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id'),
|
||||
field('source', field('country', 'US'))
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {},
|
||||
"advancedFilters": {
|
||||
"sourceCountry": "US"
|
||||
}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
And database has 1 entity CacheBundle:Query\SimpleQuery
|
||||
| id | 4 |
|
||||
| raw | cat |
|
||||
And database don't has entity CacheBundle:Query\SimpleQuery
|
||||
| id | 5 |
|
||||
| raw | cat |
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' and use `Source Country` advanced filters with empty value.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"advancedFilters": {
|
||||
"sourceCountry": ""
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id')
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {},
|
||||
"advancedFilters": {
|
||||
"sourceCountry": ""
|
||||
}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
And database has entity CacheBundle:Query\SimpleQuery
|
||||
| id | 4 |
|
||||
| raw | cat |
|
||||
|
||||
@external-index-fixtures @db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' use `Source Country` advanced filters with invalid value.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"advancedFilters": {
|
||||
"sourceCountry": "some"
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": [],
|
||||
"count": 0,
|
||||
"totalCount": 0,
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {},
|
||||
"advancedFilters": {
|
||||
"sourceCountry": "some"
|
||||
}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
And database has entity CacheBundle:Query\SimpleQuery
|
||||
| id | 4 |
|
||||
| raw | cat |
|
||||
@@ -0,0 +1,197 @@
|
||||
Feature: Use 'Source Section' advanced filter
|
||||
As an authenticated user
|
||||
I should be able to use 'Source Section' for filtering search results
|
||||
|
||||
@external-index-fixtures @db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' and use `Source Section` advanced filters with value 'Lifestyle'.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"advancedFilters": {
|
||||
"sourceSection": "Lifestyle"
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id'),
|
||||
field('source', field('section', 'Lifestyle'))
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {},
|
||||
"advancedFilters": {
|
||||
"sourceSection": "Lifestyle"
|
||||
}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
And database has 1 entity CacheBundle:Query\SimpleQuery
|
||||
| id | 4 |
|
||||
| raw | cat |
|
||||
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"advancedFilters": {
|
||||
"sourceSection": "Lifestyle"
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id'),
|
||||
field('source', field('section', 'Lifestyle'))
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {},
|
||||
"advancedFilters": {
|
||||
"sourceSection": "Lifestyle"
|
||||
}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
And database has 1 entity CacheBundle:Query\SimpleQuery
|
||||
| id | 4 |
|
||||
| raw | cat |
|
||||
And don't has entity CacheBundle:Query\SimpleQuery
|
||||
| id | 5 |
|
||||
| raw | cat |
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' and use `Source Section` advanced filters with empty value.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"advancedFilters": {
|
||||
"sourceSection": ""
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id')
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {},
|
||||
"advancedFilters": {
|
||||
"sourceSection": ""
|
||||
}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
And database has entity CacheBundle:Query\SimpleQuery
|
||||
| id | 4 |
|
||||
| raw | cat |
|
||||
|
||||
@external-index-fixtures @db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' and use `Source Section` advanced filters with invalid value.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"advancedFilters": {
|
||||
"sourceSection": "some"
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": [],
|
||||
"count": 0,
|
||||
"totalCount": 0,
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {},
|
||||
"advancedFilters": {
|
||||
"sourceSection": "some"
|
||||
}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
And database has 1 entity CacheBundle:Query\SimpleQuery
|
||||
| id | 4 |
|
||||
| raw | cat |
|
||||
@@ -0,0 +1,197 @@
|
||||
Feature: Use 'Source State' advanced filter
|
||||
As an authenticated user
|
||||
I should be able to use 'Source State' for filtering search results
|
||||
|
||||
@external-index-fixtures @db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' and use `Source State` advanced filters with value 'Arizona'.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"advancedFilters": {
|
||||
"sourceState": "Arizona"
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id'),
|
||||
field('source', field('state', 'Arizona'))
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {},
|
||||
"advancedFilters": {
|
||||
"sourceState": "Arizona"
|
||||
}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
And database has 1 entity CacheBundle:Query\SimpleQuery
|
||||
| id | 4 |
|
||||
| raw | cat |
|
||||
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"advancedFilters": {
|
||||
"sourceState": "Arizona"
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id'),
|
||||
field('source', field('state', 'Arizona'))
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {},
|
||||
"advancedFilters": {
|
||||
"sourceState": "Arizona"
|
||||
}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
And database has 1 entity CacheBundle:Query\SimpleQuery
|
||||
| id | 4 |
|
||||
| raw | cat |
|
||||
And don't has entity CacheBundle:Query\SimpleQuery
|
||||
| id | 5 |
|
||||
| raw | cat |
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' and use `Source State` advanced filters with empty value.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"advancedFilters": {
|
||||
"sourceState": ""
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id')
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {},
|
||||
"advancedFilters": {
|
||||
"sourceState": ""
|
||||
}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
And database has entity CacheBundle:Query\SimpleQuery
|
||||
| id | 4 |
|
||||
| raw | cat |
|
||||
|
||||
@external-index-fixtures @db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' and use `Source State` advanced filters with invalid value.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"advancedFilters": {
|
||||
"sourceState": "some"
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": [],
|
||||
"count": 0,
|
||||
"totalCount": 0,
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {},
|
||||
"advancedFilters": {
|
||||
"sourceState": "some"
|
||||
}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
And database has 1 entity CacheBundle:Query\SimpleQuery
|
||||
| id | 4 |
|
||||
| raw | cat |
|
||||
@@ -0,0 +1,207 @@
|
||||
Feature: Use 'Country' filter
|
||||
As an authenticated user
|
||||
I should be able to use 'Country' for filtering search results
|
||||
|
||||
@external-index-fixtures @db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' which appears in documents with US language.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"filters": {
|
||||
"country": {
|
||||
"include": [ "US" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id'),
|
||||
field('source', field('country', 'US'))
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {
|
||||
"country": {
|
||||
"include": [ "US" ]
|
||||
}
|
||||
},
|
||||
"advancedFilters": {}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
@external-index-fixtures @db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' which appears in documents not with US language.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"filters": {
|
||||
"country": {
|
||||
"exclude": [ "US" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id'),
|
||||
not(field('source', field('country', 'US')))
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {
|
||||
"country": {
|
||||
"exclude": [ "US" ]
|
||||
}
|
||||
},
|
||||
"advancedFilters": {}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
@external-index-fixtures @db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' which appears in documents with US and RU languages.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"filters": {
|
||||
"country": {
|
||||
"include": [ "US", "RU" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id'),
|
||||
field('source', oneOf(
|
||||
field('country', 'US'),
|
||||
field('country', 'RU')
|
||||
))
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {
|
||||
"country": {
|
||||
"include": [ "US", "RU" ]
|
||||
}
|
||||
},
|
||||
"advancedFilters": {}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
Scenario:
|
||||
I search 'cat' which appears in documents with unknown language.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"filters": {
|
||||
"country": {
|
||||
"include": [ "unknown" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got response with code 400
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": "@array@"
|
||||
}
|
||||
"""
|
||||
|
||||
Scenario:
|
||||
I search 'cat' which appears in documents not with unknown language.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"filters": {
|
||||
"country": {
|
||||
"exclude": [ "unknown" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got response with code 400
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": "@array@"
|
||||
}
|
||||
"""
|
||||
@@ -0,0 +1,255 @@
|
||||
Feature: Use 'Date' filter
|
||||
As an authenticated user
|
||||
I should be able to use 'Date' for filtering search results
|
||||
|
||||
@external-index-fixtures @db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' in documents which found maximum 10 days ago.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"filters": {
|
||||
"date": {
|
||||
"type": "last"
|
||||
"days": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id'),
|
||||
oneOf(
|
||||
field('title', contains('cat', true)),
|
||||
field('content', contains('cat', true))
|
||||
),
|
||||
field('published', gte('#now().modify(\"- 10 days\").format(\"c\")#'))
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@"
|
||||
}
|
||||
"""
|
||||
|
||||
@external-index-fixtures @db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' in documents which found between some period.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"filters": {
|
||||
"date": {
|
||||
"type": "between",
|
||||
"start": "#now().modify(\"- 30 days\").format(\"Y-m-d\")#",
|
||||
"end": "#now().modify(\"- 1 days\").format(\"Y-m-d\")#"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id'),
|
||||
oneOf(
|
||||
field('title', contains('cat', true)),
|
||||
field('content', contains('cat', true))
|
||||
),
|
||||
field('published', between(
|
||||
'#now().modify(\"- 30 days\").format(\"c\")#',
|
||||
'#now().modify(\"- 1 days\").format(\"c\")#'
|
||||
))
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@"
|
||||
}
|
||||
"""
|
||||
|
||||
Scenario:
|
||||
I search 'cat' in documents which filtered by date filter with invalid type.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"filters": {
|
||||
"date": {
|
||||
"type": "invalid"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got response with code 400
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": "@array@"
|
||||
}
|
||||
"""
|
||||
|
||||
Scenario:
|
||||
I search 'cat' in documents which filtered by date filter with 'last' type
|
||||
but not provide 'days' field.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"filters": {
|
||||
"date": {
|
||||
"type": "last"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got response with code 400
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": "@array@"
|
||||
}
|
||||
"""
|
||||
|
||||
Scenario Outline:
|
||||
I search 'cat' in documents which filtered by date filter with 'last' type
|
||||
but provide invalid 'days' values.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"filters": {
|
||||
"date": {
|
||||
"type": "last",
|
||||
"days": <value>
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got response with code 400
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": "@array@"
|
||||
}
|
||||
"""
|
||||
|
||||
Examples:
|
||||
| value |
|
||||
| "invalid" |
|
||||
| 0 |
|
||||
| -10 |
|
||||
|
||||
Scenario:
|
||||
I search 'cat' in documents which filtered by date filter with 'between' type
|
||||
but not provide 'start' and 'end' values.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"filters": {
|
||||
"date": {
|
||||
"type": "between"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got response with code 400
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": "@array@"
|
||||
}
|
||||
"""
|
||||
|
||||
Scenario Outline:
|
||||
I search 'cat' in documents which filtered by date filter with 'between' type
|
||||
but provide invalid 'start' and 'end' values.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"filters": {
|
||||
"date": {
|
||||
"type": "between",
|
||||
"start": "<date>",
|
||||
"end": "<date>"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got response with code 400
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": "@array@"
|
||||
}
|
||||
"""
|
||||
|
||||
Examples:
|
||||
| date |
|
||||
| 2017-13-20 |
|
||||
| some |
|
||||
| 2017-01-40 |
|
||||
|
||||
Scenario:
|
||||
I search 'cat' in documents which filtered by date filter with 'between' type
|
||||
but provide 'start' greater than 'end'.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"filters": {
|
||||
"date": {
|
||||
"type": "between",
|
||||
"start": "#now().modify(\"- 15 days\").format(\"Y-m-d\")#",
|
||||
"end": "#now().modify(\"- 30 days\").format(\"Y-m-d\")#"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got response with code 400
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": "@array@"
|
||||
}
|
||||
"""
|
||||
@@ -0,0 +1,129 @@
|
||||
Feature: Use 'Has Image' filter
|
||||
As an authenticated user
|
||||
I should be able to use 'Has Image' for filtering search results
|
||||
|
||||
@external-index-fixtures @db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' which appears in documents which have image.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"filters": {
|
||||
"hasImage": true
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id'),
|
||||
field('image', isNotEmpty())
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {
|
||||
"hasImage": true
|
||||
},
|
||||
"advancedFilters": {}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
@external-index-fixtures @db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' which appears in documents which is may have images.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"filters": {
|
||||
"hasImage": false
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id'),
|
||||
oneOf(
|
||||
field('image', isEmpty()),
|
||||
field('image', isNotEmpty())
|
||||
)
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {
|
||||
"hasImage": false
|
||||
},
|
||||
"advancedFilters": {}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
Scenario Outline:
|
||||
I search 'cat' which appears in documents which filtered by hasImage filters
|
||||
with invalid value.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"filters": {
|
||||
"hasImage": <value>
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got response with code 400
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": "@array@"
|
||||
}
|
||||
"""
|
||||
|
||||
Examples:
|
||||
| value |
|
||||
| "some" |
|
||||
| 10 |
|
||||
| "true" |
|
||||
@@ -0,0 +1,344 @@
|
||||
Feature: Use 'Headline' filter
|
||||
As an authenticated user
|
||||
I should be able to use 'Headline' for filtering search results
|
||||
|
||||
@external-index-fixtures @db-fixtures
|
||||
Scenario:
|
||||
I search 'cat'.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"filters": {}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id'),
|
||||
oneOf(
|
||||
field('title', contains('cat', true)),
|
||||
field('title', contains('dog', true)),
|
||||
field('title', contains('fish', true)),
|
||||
field('content', contains('cat', true)),
|
||||
field('content', contains('dog', true)),
|
||||
field('content', contains('fish', true))
|
||||
)
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {},
|
||||
"advancedFilters": {}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
@external-index-fixtures @db-fixtures
|
||||
Scenario:
|
||||
I search 'cat', and also include 'dog' in headline.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"filters": {
|
||||
"headline": {
|
||||
"include": "dog"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id'),
|
||||
oneOf(
|
||||
field('title', contains('cat', true)),
|
||||
field('content', contains('cat', true))
|
||||
),
|
||||
field('title',
|
||||
contains('cat', true),
|
||||
contains('dog', true)
|
||||
)
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {
|
||||
"headline": {
|
||||
"include": "dog"
|
||||
}
|
||||
},
|
||||
"advancedFilters": {}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
@external-index-fixtures @db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' which not include 'cat' in headline.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"filters": {
|
||||
"headline": {
|
||||
"exclude": "cat"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id'),
|
||||
oneOf(
|
||||
field('title', contains('cat', true)),
|
||||
field('content', contains('cat', true))
|
||||
),
|
||||
field('title',
|
||||
not(contains('cat', true)),
|
||||
contains('some', true)
|
||||
),
|
||||
field('content', contains('cat', true))
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {
|
||||
"headline": {
|
||||
"exclude": "cat"
|
||||
}
|
||||
},
|
||||
"advancedFilters": {}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
@external-index-fixtures @db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' without 'dog' and 'fish' in headline.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"filters": {
|
||||
"headline": {
|
||||
"exclude": "dog, fish"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id'),
|
||||
oneOf(
|
||||
field('title', contains('cat', true)),
|
||||
field('content', contains('cat', true))
|
||||
),
|
||||
field('title',
|
||||
not(contains('dog', true)),
|
||||
not(contains('fish', true))
|
||||
)
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {
|
||||
"headline": {
|
||||
"exclude": "dog, fish"
|
||||
}
|
||||
},
|
||||
"advancedFilters": {}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
@external-index-fixtures @db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' with 'dog' but without 'fish' in headline.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"filters": {
|
||||
"headline": {
|
||||
"include": "dog",
|
||||
"exclude": "fish"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id'),
|
||||
oneOf(
|
||||
field('title', contains('cat', true)),
|
||||
field('content', contains('cat', true))
|
||||
),
|
||||
field('title',
|
||||
contains('cat', true),
|
||||
contains('dog', true),
|
||||
not(contains('fish', true))
|
||||
)
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {
|
||||
"headline": {
|
||||
"include": "dog",
|
||||
"exclude": "fish"
|
||||
}
|
||||
},
|
||||
"advancedFilters": {}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
@external-index-fixtures @db-fixtures
|
||||
Scenario:
|
||||
I search documents 'fish' without 'cat' in headline.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "fish",
|
||||
"page": 1,
|
||||
"filters": {
|
||||
"headline": {
|
||||
"exclude": "cat"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id'),
|
||||
oneOf(
|
||||
field('title', contains('fish', true)),
|
||||
field('content', contains('fish', true))
|
||||
),
|
||||
field('title', not(contains('cat', true)))
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "fish",
|
||||
"filters": {
|
||||
"headline": {
|
||||
"exclude": "cat"
|
||||
}
|
||||
},
|
||||
"advancedFilters": {}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
@@ -0,0 +1,168 @@
|
||||
Feature: Use 'Language' filter
|
||||
As an authenticated user
|
||||
I should be able to use 'Language' for filtering search results
|
||||
|
||||
@external-index-fixtures @db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' which appears in documents with english language.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"filters": {
|
||||
"language": [ "en" ]
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id'),
|
||||
field('language', 'en')
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {
|
||||
"language": [ "en" ]
|
||||
},
|
||||
"advancedFilters": {}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
@external-index-fixtures @db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' which appears in documents with english and russian languages.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"filters": {
|
||||
"language": [ "en", "ru" ]
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id'),
|
||||
oneOf(
|
||||
field('language', 'en'),
|
||||
field('language', 'ru')
|
||||
)
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {
|
||||
"language": [ "en", "ru" ]
|
||||
},
|
||||
"advancedFilters": {}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
Scenario:
|
||||
I search 'cat' which appears in documents with unknown languages.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"filters": {
|
||||
"language": [ "unknown" ]
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got response with code 400
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": "@array@"
|
||||
}
|
||||
"""
|
||||
|
||||
@external-index-fixtures @db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' which appears in documents with empty language filters.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"filters": {
|
||||
"language": [ ]
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id')
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {
|
||||
"language": []
|
||||
},
|
||||
"advancedFilters": {}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
@@ -0,0 +1,207 @@
|
||||
Feature: Use 'State' filter
|
||||
As an authenticated user
|
||||
I should be able to use 'State' for filtering search results
|
||||
|
||||
@external-index-fixtures @db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' which appears in documents from state Arizona.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"filters": {
|
||||
"state": {
|
||||
"include": [ "AZ" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id'),
|
||||
field('source', field('state', 'Arizona'))
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {
|
||||
"state": {
|
||||
"include": [ "AZ" ]
|
||||
}
|
||||
},
|
||||
"advancedFilters": {}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
@external-index-fixtures @db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' which appears in documents not from state Arizona.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"filters": {
|
||||
"state": {
|
||||
"exclude": [ "AZ" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id'),
|
||||
not(field('source', field('state', 'Arizona')))
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {
|
||||
"state": {
|
||||
"exclude": [ "AZ" ]
|
||||
}
|
||||
},
|
||||
"advancedFilters": {}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
@external-index-fixtures @db-fixtures
|
||||
Scenario:
|
||||
I search 'cat' which appears in documents not from Louisiana and Maryland.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"filters": {
|
||||
"state": {
|
||||
"include": [ "LA", "MD" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"documents": {
|
||||
"data": "@array@.every(
|
||||
entity('CacheBundle:Document', 'document, id'),
|
||||
field('source', oneOf(
|
||||
field('state', 'Louisiana'),
|
||||
field('state', 'Maryland')
|
||||
))
|
||||
)",
|
||||
"count": "@integer@.greaterThan(0)",
|
||||
"totalCount": "@integer@.greaterThan(0)",
|
||||
"page": 1,
|
||||
"limit": 100
|
||||
},
|
||||
"advancedFilters": "@array@",
|
||||
"stats": "@object@",
|
||||
"meta": {
|
||||
"type": "query",
|
||||
"status": "synced",
|
||||
"search": {
|
||||
"query": "cat",
|
||||
"filters": {
|
||||
"state": {
|
||||
"include": [ "LA", "MD" ]
|
||||
}
|
||||
},
|
||||
"advancedFilters": {}
|
||||
},
|
||||
"sources": [],
|
||||
"sourceLists": []
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
Scenario:
|
||||
I search 'cat' which appears in documents from unknown state.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"filters": {
|
||||
"state": {
|
||||
"include": [ "unknown" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got response with code 400
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": "@array@"
|
||||
}
|
||||
"""
|
||||
|
||||
Scenario:
|
||||
I search 'cat' which appears in documents not from unknown state.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/query/search
|
||||
"""
|
||||
{
|
||||
"query": "cat",
|
||||
"page": 1,
|
||||
"filters": {
|
||||
"state": {
|
||||
"exclude": [ "unknown" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then I got response with code 400
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": "@array@"
|
||||
}
|
||||
"""
|
||||
@@ -0,0 +1,34 @@
|
||||
Feature: Create recipient group
|
||||
As an master
|
||||
I should be able to create group of recipients
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I try to create recipient group.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/recipients/groups
|
||||
"""
|
||||
{
|
||||
"name": "Test Group",
|
||||
"description": "some group",
|
||||
"active": true,
|
||||
"recipients": [],
|
||||
"notifications": []
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
@object@
|
||||
.entity('UserBundle:GroupRecipient', 'recipient, id')
|
||||
.field('name', 'Test Group')
|
||||
.field('description', 'some group')
|
||||
.field('active', true)
|
||||
.field('recipients', [])
|
||||
.field('notifications', [])
|
||||
"""
|
||||
And database has entity UserBundle:GroupRecipient
|
||||
| name | Test Group |
|
||||
| description | some group |
|
||||
| active | true |
|
||||
@@ -0,0 +1,36 @@
|
||||
Feature: Create recipient
|
||||
As an master
|
||||
I must be able to create new person recipient
|
||||
|
||||
@db-fixtures
|
||||
Scenario:
|
||||
I try to create new recipient.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
When I make POST request to /api/v1/recipients
|
||||
"""
|
||||
{
|
||||
"firstName": "Test",
|
||||
"lastName": "User",
|
||||
"email": "test.user@email.com",
|
||||
"active": true,
|
||||
"notifications": [],
|
||||
"groups": []
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
@object@
|
||||
.entity('UserBundle:PersonRecipient', 'recipient, id')
|
||||
.field('firstName', 'Test')
|
||||
.field('lastName', 'User')
|
||||
.field('email', 'test.user@email.com')
|
||||
.field('active', true)
|
||||
.field('groups', [])
|
||||
"""
|
||||
And database has entity UserBundle:PersonRecipient
|
||||
| firstName | Test |
|
||||
| lastName | User |
|
||||
| email | test.user@email.com |
|
||||
| active | true |
|
||||
@@ -0,0 +1,63 @@
|
||||
Feature: Add sources to list
|
||||
As an authenticated user
|
||||
I should be able to place sources in my lists
|
||||
|
||||
@db-fixtures @source-index-fixtures
|
||||
Scenario:
|
||||
I make empty request.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
And I make POST request to /api/v1/source-index/add-to-sources-list
|
||||
"""
|
||||
{
|
||||
"sources": [ 1, 2 ],
|
||||
"sourceLists": [ 1, 3, 5, 7, 9, 11 ]
|
||||
}
|
||||
"""
|
||||
Then I got response with code 204
|
||||
|
||||
# Check database.
|
||||
And database has entity CacheBundle:SourceToSourceList
|
||||
| source | 1 |
|
||||
| list | 1 |
|
||||
And has entity CacheBundle:SourceToSourceList
|
||||
| source | 1 |
|
||||
| list | 3 |
|
||||
And has entity CacheBundle:SourceToSourceList
|
||||
| source | 1 |
|
||||
| list | 5 |
|
||||
And has entity CacheBundle:SourceToSourceList
|
||||
| source | 1 |
|
||||
| list | 7 |
|
||||
And has entity CacheBundle:SourceToSourceList
|
||||
| source | 1 |
|
||||
| list | 9 |
|
||||
And has entity CacheBundle:SourceToSourceList
|
||||
| source | 1 |
|
||||
| list | 11 |
|
||||
|
||||
And has entity CacheBundle:SourceToSourceList
|
||||
| source | 2 |
|
||||
| list | 1 |
|
||||
And has entity CacheBundle:SourceToSourceList
|
||||
| source | 2 |
|
||||
| list | 3 |
|
||||
And has entity CacheBundle:SourceToSourceList
|
||||
| source | 2 |
|
||||
| list | 5 |
|
||||
And has entity CacheBundle:SourceToSourceList
|
||||
| source | 2 |
|
||||
| list | 7 |
|
||||
And has entity CacheBundle:SourceToSourceList
|
||||
| source | 2 |
|
||||
| list | 9 |
|
||||
And has entity CacheBundle:SourceToSourceList
|
||||
| source | 2 |
|
||||
| list | 11 |
|
||||
|
||||
And I wait 1000 milliseconds
|
||||
|
||||
# Check index.
|
||||
And source index has 2 documents
|
||||
| _id | in | 1, 2 |
|
||||
| list_ids | in | 1, 3, 5, 7, 9, 11 |
|
||||
@@ -0,0 +1,120 @@
|
||||
Feature: Get list of sources
|
||||
As an authenticated user
|
||||
I should be able to get list of sources
|
||||
|
||||
@db-fixtures @source-index-fixtures
|
||||
Scenario:
|
||||
I make empty request.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
And I make POST request to /api/v1/source-index/
|
||||
"""
|
||||
{
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"sources": {
|
||||
"data": "@array@",
|
||||
"count": "@integer@",
|
||||
"totalCount": "@integer@",
|
||||
"page": 1,
|
||||
"limit": 20
|
||||
},
|
||||
"filters": "@object@"
|
||||
}
|
||||
"""
|
||||
|
||||
@db-fixtures @source-index-fixtures
|
||||
Scenario:
|
||||
I search for source with 'CNN' in title or url.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
And I make POST request to /api/v1/source-index/
|
||||
"""
|
||||
{
|
||||
"query": "CNN"
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"sources": {
|
||||
"data": "@array@
|
||||
.every(field('title', contains('CNN')))
|
||||
",
|
||||
"count": "@integer@",
|
||||
"totalCount": "@integer@",
|
||||
"page": 1,
|
||||
"limit": 20
|
||||
},
|
||||
"filters": "@object@"
|
||||
}
|
||||
"""
|
||||
|
||||
@db-fixtures @source-index-fixtures
|
||||
Scenario Outline:
|
||||
I should be able to get more or less source per page.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
And I make POST request to /api/v1/source-index/
|
||||
"""
|
||||
{
|
||||
"limit": <limit>
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"sources": {
|
||||
"data": "@array@",
|
||||
"count": "@integer@",
|
||||
"totalCount": "@integer@",
|
||||
"page": 1,
|
||||
"limit": <limit>
|
||||
},
|
||||
"filters": "@object@"
|
||||
}
|
||||
"""
|
||||
|
||||
Examples:
|
||||
| limit |
|
||||
| 10 |
|
||||
| 1 |
|
||||
| 200 |
|
||||
|
||||
@db-fixtures @source-index-fixtures
|
||||
Scenario Outline:
|
||||
I should'nt be able change requested page.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
And I make POST request to /api/v1/source-index/
|
||||
"""
|
||||
{
|
||||
"page": <page>
|
||||
}
|
||||
"""
|
||||
Then I got successful response
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"sources": {
|
||||
"data": "@array@",
|
||||
"count": "@integer@",
|
||||
"totalCount": "@integer@",
|
||||
"page": <page>,
|
||||
"limit": 20
|
||||
},
|
||||
"filters": "@object@"
|
||||
}
|
||||
"""
|
||||
|
||||
Examples:
|
||||
| page |
|
||||
| 2 |
|
||||
| 3 |
|
||||
| 20 |
|
||||
@@ -0,0 +1,133 @@
|
||||
Feature: Replace used lists for source
|
||||
As an authenticated user
|
||||
I should be able to replace used lists for specified source
|
||||
|
||||
@db-fixtures @source-index-fixtures
|
||||
Scenario:
|
||||
I replace lists for one of source.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
And I make POST request to /api/v1/source-index/1/list
|
||||
"""
|
||||
{
|
||||
"sourceLists": [ 1, 3, 5, 7, 9, 11 ]
|
||||
}
|
||||
"""
|
||||
Then I got response with code 204
|
||||
|
||||
# Check database.
|
||||
And database has entity CacheBundle:SourceToSourceList
|
||||
| source | 1 |
|
||||
| list | 1 |
|
||||
And has entity CacheBundle:SourceToSourceList
|
||||
| source | 1 |
|
||||
| list | 3 |
|
||||
And has entity CacheBundle:SourceToSourceList
|
||||
| source | 1 |
|
||||
| list | 5 |
|
||||
And has entity CacheBundle:SourceToSourceList
|
||||
| source | 1 |
|
||||
| list | 7 |
|
||||
And has entity CacheBundle:SourceToSourceList
|
||||
| source | 1 |
|
||||
| list | 9 |
|
||||
And has entity CacheBundle:SourceToSourceList
|
||||
| source | 1 |
|
||||
| list | 11 |
|
||||
|
||||
And I wait 1000 milliseconds
|
||||
|
||||
# Check index.
|
||||
And source index has 1 documents
|
||||
| _id | in | 1 |
|
||||
| list_ids | in | 1, 3, 5, 7, 9, 11 |
|
||||
|
||||
|
||||
@db-fixtures @source-index-fixtures
|
||||
Scenario:
|
||||
I replace lists for unknown source.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
And I make POST request to /api/v1/source-index/10000/list
|
||||
"""
|
||||
{
|
||||
"sourceLists": [ 1, 3, 5, 7, 9, 11 ]
|
||||
}
|
||||
"""
|
||||
Then I got response with code 404
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"message": "Can't find source with id 10000",
|
||||
"transKey": "replaceSourceUnknown",
|
||||
"type": "error",
|
||||
"parameters": {
|
||||
"current": "10000"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
@db-fixtures @source-index-fixtures
|
||||
Scenario:
|
||||
I make empty request.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
And I make POST request to /api/v1/source-index/1/list
|
||||
"""
|
||||
{
|
||||
}
|
||||
"""
|
||||
Then I got response with code 400
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"message": "sourceLists: This value should not be empty.",
|
||||
"transKey": "replaceSourceListsEmpty",
|
||||
"type": "error",
|
||||
"parameters": {
|
||||
"current": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
@db-fixtures @source-index-fixtures
|
||||
Scenario Outline:
|
||||
I replace lists for unknown or not owned lists.
|
||||
|
||||
Given I authenticated as test@email.com with password test
|
||||
And I make POST request to /api/v1/source-index/1/list
|
||||
"""
|
||||
{
|
||||
"sourceLists": [ <list_id> ]
|
||||
}
|
||||
"""
|
||||
Then I got response with code 400
|
||||
And it's contains
|
||||
"""
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"message": "sourceLists: This value is invalid.",
|
||||
"transKey": "replaceSourceListInvalid",
|
||||
"type": "error",
|
||||
"parameters": {
|
||||
"current": [ <list_id> ],
|
||||
"invalid": [ <list_id> ]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
Examples:
|
||||
| list_id |
|
||||
| 1000 |
|
||||
| 2 |
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace Command\Context;
|
||||
|
||||
use Behat\Gherkin\Node\TableNode;
|
||||
use Command\Util\CommandTest;
|
||||
use Command\Util\CommandTestFactory;
|
||||
use Common\Context\AbstractContext;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpKernel\KernelInterface;
|
||||
|
||||
/**
|
||||
* Class CommandContext
|
||||
* @package Command\Context
|
||||
*/
|
||||
class CommandContext extends AbstractContext
|
||||
{
|
||||
|
||||
/**
|
||||
* @var CommandTestFactory
|
||||
*/
|
||||
private $factory;
|
||||
|
||||
/**
|
||||
* @var CommandTest
|
||||
*/
|
||||
private $command;
|
||||
|
||||
/**
|
||||
* @param ContainerInterface $container A ContainerInterface instance.
|
||||
* @param string $fixturesDir Path to fixtures directory.
|
||||
*/
|
||||
public function __construct(ContainerInterface $container, $fixturesDir)
|
||||
{
|
||||
parent::__construct($container, $fixturesDir);
|
||||
|
||||
/** @var KernelInterface $kernel */
|
||||
$kernel = $container->get('kernel');
|
||||
$this->factory = new CommandTestFactory($kernel);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given /^I run command (?P<command>.+)$/
|
||||
*
|
||||
* @param string $name Command name.
|
||||
* @param TableNode $table Command parameters in table format.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function runCommand($name, TableNode $table = null)
|
||||
{
|
||||
$params = [];
|
||||
if ($table !== null) {
|
||||
foreach ($table as $row) {
|
||||
$params[current($row)] = next($row);
|
||||
}
|
||||
}
|
||||
|
||||
$this->command = $this->factory->create($name, $params)->run();
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^(?:|[Cc]ommand )[Rr]eturned (?P<code>\d+) exit code$/
|
||||
*
|
||||
* @param integer $code Command exit code.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function checkExitCode($code = 0)
|
||||
{
|
||||
self::assertEquals($code, $this->command->getExitCode());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace Command\Util;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
use Symfony\Component\Console\Input\ArrayInput;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\BufferedOutput;
|
||||
|
||||
/**
|
||||
* Class CommandTest
|
||||
* @package Command\Util
|
||||
*/
|
||||
class CommandTest
|
||||
{
|
||||
|
||||
/**
|
||||
* @var Application
|
||||
*/
|
||||
private $application;
|
||||
|
||||
/**
|
||||
* @var InputInterface
|
||||
*/
|
||||
private $input;
|
||||
|
||||
/**
|
||||
* @var BufferedOutput
|
||||
*/
|
||||
private $output;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
private $exitCode;
|
||||
|
||||
/**
|
||||
* Command constructor.
|
||||
*
|
||||
* @param Application $application A Application instance.
|
||||
* @param string $command Command name.
|
||||
* @param array $params Command parameters.
|
||||
*/
|
||||
public function __construct(
|
||||
Application $application,
|
||||
$command,
|
||||
array $params = []
|
||||
) {
|
||||
$this->application = $application;
|
||||
$this->input = new ArrayInput([ 'command' => $command ] + $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run this command.
|
||||
*
|
||||
* @return CommandTest
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
$this->output = new BufferedOutput();
|
||||
$this->exitCode = $this->application->run($this->input, $this->output);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get exit code.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getExitCode()
|
||||
{
|
||||
return $this->exitCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get output.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getOutput()
|
||||
{
|
||||
return $this->output->fetch();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Command\Util;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
use Symfony\Component\HttpKernel\KernelInterface;
|
||||
|
||||
/**
|
||||
* Class CommandTestFactory
|
||||
* @package Command\Util
|
||||
*/
|
||||
class CommandTestFactory
|
||||
{
|
||||
|
||||
/**
|
||||
* @var Application
|
||||
*/
|
||||
private $application;
|
||||
|
||||
/**
|
||||
* CommandRunner constructor.
|
||||
*
|
||||
* @param KernelInterface $kernel A KernelInterface instance.
|
||||
*/
|
||||
public function __construct(KernelInterface $kernel)
|
||||
{
|
||||
$this->application = new Application($kernel);
|
||||
// If don't set to false, application will call 'exit' after command was
|
||||
// executed.
|
||||
$this->application->setAutoExit(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new command instance.
|
||||
*
|
||||
* @param string $command Command name.
|
||||
* @param array $params Command parameters.
|
||||
*
|
||||
* @return CommandTest
|
||||
*/
|
||||
public function create($command, array $params = [])
|
||||
{
|
||||
return new CommandTest($this->application, $command, $params);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
Feature:
|
||||
Application should fetch document for stored queries in background.
|
||||
So we must have command that do it.
|
||||
|
||||
@db-fixtures @external-index-fixtures
|
||||
Scenario:
|
||||
Fetch document for stored queries.
|
||||
|
||||
|
||||
Given I run command socialhose:stored_query:fetch
|
||||
|
||||
Then command returned 0 exit code
|
||||
|
||||
# Check entities in database.
|
||||
And database has entity CacheBundle:Query\StoredQuery
|
||||
| raw | cat |
|
||||
| status | synced |
|
||||
And has entity CacheBundle:Document
|
||||
| title | About cat |
|
||||
And don't has entity AppBundle:FetchJob
|
||||
| id | 1 |
|
||||
|
||||
When I wait 1000 milliseconds until documents was indexed
|
||||
Then internal index has 1 document
|
||||
| query | eq | #getStoredQuery({'raw': 'cat', 'status': 'synced'}).getId()# |
|
||||
|
||||
# After add new document.
|
||||
Given has new document in external index
|
||||
| sequence | 2 |
|
||||
| title | New cat article |
|
||||
| date_found | #date().getTimestamp()# |
|
||||
|
||||
When I run command socialhose:stored_query:update
|
||||
Then command returned 0 exit code
|
||||
|
||||
# Check entities in database.
|
||||
And database has entity CacheBundle:Query\StoredQuery
|
||||
| raw | cat |
|
||||
| status | synced |
|
||||
And has entity CacheBundle:Document
|
||||
| title | About cat |
|
||||
And has entity CacheBundle:Document
|
||||
| title | New cat article |
|
||||
|
||||
When I wait 1000 milliseconds until documents was indexed
|
||||
Then internal index has 2 document
|
||||
| query | eq | #getStoredQuery({'raw': 'cat', 'status': 'synced'}).getId()# |
|
||||
|
||||
# Add third document.
|
||||
Given has new document in external index
|
||||
| sequence | 3 |
|
||||
| title | About dogs |
|
||||
| date_found | #date().getTimestamp()# |
|
||||
|
||||
When I run command socialhose:stored_query:update
|
||||
Then command returned 0 exit code
|
||||
|
||||
# Check entities in database.
|
||||
And has entity CacheBundle:Document
|
||||
| title | About cat |
|
||||
And has entity CacheBundle:Document
|
||||
| title | New cat article |
|
||||
But don't has entity CacheBundle:Document
|
||||
| title | About dogs |
|
||||
|
||||
When I wait 1000 milliseconds until documents was indexed
|
||||
Then internal index has 2 document
|
||||
| query | eq | #getStoredQuery({'raw': 'cat', 'status': 'synced'}).getId()# |
|
||||
@@ -0,0 +1,338 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Context;
|
||||
|
||||
use Behat\Behat\Context\Context;
|
||||
use Behat\Gherkin\Node\PyStringNode;
|
||||
use Common\Util\DatabaseHelper;
|
||||
use Common\Util\Index\InternalSourceConnection;
|
||||
use Common\Util\Processor\DataProcessor;
|
||||
use Common\Util\Index\ExternalIndexConnection;
|
||||
use Common\Util\Index\InternalIndexConnection;
|
||||
use Common\Util\Matcher\AppMatcher;
|
||||
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use IndexBundle\Fixture\Executor\Factory\IndexFixtureExecutorFactory;
|
||||
use IndexBundle\Fixture\Executor\Factory\IndexFixtureExecutorFactoryInterface;
|
||||
use IndexBundle\Fixture\Loader\IndexFixtureLoader;
|
||||
use Seld\JsonLint\JsonParser;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Class AbstractContext
|
||||
* Base class for all application test context's.
|
||||
*
|
||||
* @package Common\Context
|
||||
*/
|
||||
class AbstractContext extends \PHPUnit_Framework_Assert implements Context
|
||||
{
|
||||
|
||||
use DatabaseContextTrait,
|
||||
IndexContextTrait;
|
||||
|
||||
/**
|
||||
* True if test running with debug.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $debug;
|
||||
|
||||
/**
|
||||
* @var ContainerInterface
|
||||
*/
|
||||
protected $container;
|
||||
|
||||
/**
|
||||
* Path to data fixtures directory.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fixturesDir;
|
||||
|
||||
/**
|
||||
* @var DataProcessor
|
||||
*/
|
||||
protected $processor;
|
||||
|
||||
/**
|
||||
* @var IndexFixtureExecutorFactoryInterface
|
||||
*/
|
||||
protected $indexExecutorFactory;
|
||||
|
||||
/**
|
||||
* True if all indices initialized.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
private static $indexInitialized = false;
|
||||
|
||||
/**
|
||||
* Setup database schemas.
|
||||
*
|
||||
* @BeforeSuite
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function setup()
|
||||
{
|
||||
if (strtolower(trim(getenv('WITHOUT_CLEAR'))) !== 'true') {
|
||||
system('./bin/console --env=test cache:clear');
|
||||
system('./bin/console --env=test doctrine:database:create --if-not-exists -n');
|
||||
system('./bin/console --env=test doctrine:schema:update --force -n');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear external index and load fixtures before scenario.
|
||||
*
|
||||
* @BeforeScenario @external-index-fixtures
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setupExternalIndexFixtures()
|
||||
{
|
||||
$this->createIndexes();
|
||||
|
||||
echo 'Purge external index ... ';
|
||||
$this->externalIndex->purge();
|
||||
echo 'done'. PHP_EOL;
|
||||
|
||||
echo 'Load indices fixtures: '. PHP_EOL;
|
||||
|
||||
$loader = new IndexFixtureLoader($this->container);
|
||||
$loader->loadFromDirectory($this->fixturesDir);
|
||||
$this->indexExecutorFactory->external($this->externalIndex->getIndex())
|
||||
->setLogger(function ($message) {
|
||||
echo " > {$message}". PHP_EOL;
|
||||
})
|
||||
->execute($loader->getFixtures());
|
||||
|
||||
// Wait to insure that all fixtures was indexed.
|
||||
sleep(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear external index and load fixtures before scenario.
|
||||
*
|
||||
* @BeforeScenario @internal-index-fixtures
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setupInternalIndexFixtures()
|
||||
{
|
||||
$this->createIndexes();
|
||||
|
||||
echo 'Purge internal index ... ';
|
||||
$this->internalIndex->purge();
|
||||
echo 'done'. PHP_EOL;
|
||||
|
||||
echo 'Load indices fixtures: '. PHP_EOL;
|
||||
|
||||
$loader = new IndexFixtureLoader($this->container);
|
||||
$loader->loadFromDirectory($this->fixturesDir);
|
||||
$this->indexExecutorFactory->internal($this->internalIndex->getIndex())
|
||||
->setLogger(function ($message) {
|
||||
echo " > {$message}". PHP_EOL;
|
||||
})
|
||||
->execute($loader->getFixtures());
|
||||
|
||||
// Wait to insure that all fixtures was indexed.
|
||||
sleep(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear external index and load fixtures before scenario.
|
||||
*
|
||||
* @BeforeScenario @source-index-fixtures
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setupSourceIndexFixtures()
|
||||
{
|
||||
//
|
||||
// Remove source_update.date
|
||||
//
|
||||
// NOTICE: Insure that you don't run test in production :)
|
||||
//
|
||||
unlink(realpath(__DIR__ . '/../../../var/source_update.date'));
|
||||
|
||||
$this->createIndexes();
|
||||
$this->setupExternalIndexFixtures();
|
||||
|
||||
echo 'Purge source index ... ';
|
||||
$this->sourceIndex->purge();
|
||||
echo 'done'. PHP_EOL;
|
||||
|
||||
echo 'Load indices fixtures: '. PHP_EOL;
|
||||
|
||||
$loader = new IndexFixtureLoader($this->container);
|
||||
$loader->loadFromDirectory($this->fixturesDir);
|
||||
$this->indexExecutorFactory->source($this->sourceIndex->getIndex())
|
||||
->setLogger(function ($message) {
|
||||
echo " > {$message}". PHP_EOL;
|
||||
})
|
||||
->execute($loader->getFixtures());
|
||||
|
||||
// Wait to insure that all fixtures was indexed.
|
||||
sleep(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create indexes if we want to upload index fixtures.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function createIndexes()
|
||||
{
|
||||
if (! self::$indexInitialized
|
||||
&& (strtolower(trim(getenv('WITHOUT_CLEAR'))) !== 'true')) {
|
||||
$this->externalIndex->setup();
|
||||
$this->internalIndex->setup();
|
||||
$this->sourceIndex->setup();
|
||||
|
||||
self::$indexInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ContainerInterface $container A ContainerInterface instance.
|
||||
* @param string $fixturesDir Path to fixtures directory.
|
||||
*/
|
||||
public function __construct(ContainerInterface $container, $fixturesDir)
|
||||
{
|
||||
$this->debug = getenv('DEBUG') !== false;
|
||||
|
||||
$this->container = $container;
|
||||
$this->fixturesDir = realpath($fixturesDir);
|
||||
|
||||
if ($container->getParameter('kernel.environment') !== 'test') {
|
||||
$message = 'You should run test in test environment /:|';
|
||||
throw new \InvalidArgumentException($message);
|
||||
}
|
||||
|
||||
// Create database helper.
|
||||
// See Common\Context\DatabaseContextTrait
|
||||
$this->dataBaseHelper = new DatabaseHelper($container->get('doctrine'));
|
||||
|
||||
// Get serializer metadata for all entities.
|
||||
// We make it for simplification testing process. With this information
|
||||
// we can make more powerful expanders and matchers which help as to
|
||||
// write less but make more.
|
||||
/** @var EntityManagerInterface $em */
|
||||
$em = $this->container->get('doctrine.orm.default_entity_manager');
|
||||
|
||||
// Get all available entity fqcn's.
|
||||
$fqcnList = array_map(function (ClassMetadata $metadata) {
|
||||
return $metadata->getName();
|
||||
}, $em->getMetadataFactory()->getAllMetadata());
|
||||
|
||||
$entities = $this->processEntityMetadata($em, $fqcnList);
|
||||
// Register all entities.
|
||||
AppMatcher::registerEntities($entities);
|
||||
|
||||
$this->processor = new DataProcessor($container);
|
||||
|
||||
// Decorate indices connections.
|
||||
$this->externalIndex = new ExternalIndexConnection(
|
||||
$this->get('index.external')
|
||||
);
|
||||
|
||||
$this->internalIndex = new InternalIndexConnection(
|
||||
$this->get('index.articles')
|
||||
);
|
||||
|
||||
$this->sourceIndex = new InternalSourceConnection(
|
||||
$this->get('index.sources')
|
||||
);
|
||||
|
||||
$this->indexExecutorFactory = new IndexFixtureExecutorFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^(?:|I )[Ww]ait (?P<milliseconds>\d+) millisecond(?: until| for)?[\w\s]*$/
|
||||
*
|
||||
* @param integer $milliseconds Seconds count.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function wait($milliseconds)
|
||||
{
|
||||
usleep($milliseconds * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a service.
|
||||
*
|
||||
* @param string $id The service identifier.
|
||||
*
|
||||
* @return object The associated service.
|
||||
*/
|
||||
protected function get($id)
|
||||
{
|
||||
return $this->container->get($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a parameter.
|
||||
*
|
||||
* @param string $name The parameter name.
|
||||
*
|
||||
* @return mixed The parameter value.
|
||||
*/
|
||||
protected function getParameter($name)
|
||||
{
|
||||
return $this->container->getParameter($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Match value against specified pattern.
|
||||
*
|
||||
* @param mixed $value Matched value.
|
||||
* @param mixed $pattern Pattern.
|
||||
* @param string $error Occurred error.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function match($value, $pattern, &$error)
|
||||
{
|
||||
$value = $this->processor->process($value);
|
||||
|
||||
$pattern = preg_replace('/\\s{2,}/', '', $pattern);
|
||||
|
||||
// Lint pattern only if it contains json.
|
||||
if (($pattern[0] === '{') || ($pattern[0] === '[')) {
|
||||
// Lint pattern.
|
||||
$lint = new JsonParser();
|
||||
$exception = $lint->lint($pattern);
|
||||
if ($exception !== null) {
|
||||
throw new \RuntimeException('Pattern lint: ' . $exception->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Process expressions between ##.
|
||||
$pattern = $this->processor->process($pattern);
|
||||
|
||||
return AppMatcher::match($value, $pattern, $error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lint json.
|
||||
*
|
||||
* @param PyStringNode|string $json Json to lint.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function lintJson($json)
|
||||
{
|
||||
if ($json instanceof PyStringNode) {
|
||||
$json = $json->getRaw();
|
||||
}
|
||||
|
||||
$linter = new JsonParser();
|
||||
$exception = $linter->lint($json);
|
||||
|
||||
if ($exception !== null) {
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Context;
|
||||
|
||||
use ApiBundle\Entity\NormalizableEntityInterface;
|
||||
use ApiBundle\Serializer\Metadata\Metadata;
|
||||
use AppBundle\Utils\Purger\TruncateORMPurger;
|
||||
use Behat\Gherkin\Node\TableNode;
|
||||
use Common\Util\DatabaseHelper;
|
||||
use Common\Util\Metadata\EntityMetadata;
|
||||
use Doctrine\Common\DataFixtures\Executor\ORMExecutor;
|
||||
use Doctrine\Common\DataFixtures\Purger\ORMPurger;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Mapping\ClassMetadataInfo;
|
||||
use Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader;
|
||||
|
||||
/**
|
||||
* Class DatabaseContextTrait
|
||||
* Contains steps definitions for working with database.
|
||||
*
|
||||
* @package Common\Context
|
||||
*/
|
||||
trait DatabaseContextTrait
|
||||
{
|
||||
|
||||
/**
|
||||
* @var DatabaseHelper
|
||||
*/
|
||||
private $dataBaseHelper;
|
||||
|
||||
/**
|
||||
* Clear database and load fixtures before scenario.
|
||||
*
|
||||
* @BeforeScenario @db-fixtures
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setupDbFixtures()
|
||||
{
|
||||
/** @var EntityManagerInterface $em */
|
||||
$em = $this->get('doctrine.orm.entity_manager');
|
||||
|
||||
echo 'Purge database ... ';
|
||||
$purger = new TruncateORMPurger(new ORMPurger($em));
|
||||
$purger->purge();
|
||||
echo 'done'. PHP_EOL;
|
||||
|
||||
$loader = new ContainerAwareLoader($this->container);
|
||||
$loader->loadFromDirectory($this->fixturesDir);
|
||||
|
||||
echo 'Load database fixtures:'. PHP_EOL;
|
||||
$executor = new ORMExecutor($em);
|
||||
$executor
|
||||
->setLogger(function ($message) {
|
||||
echo " > {$message}". PHP_EOL;
|
||||
});
|
||||
$executor->execute($loader->getFixtures(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^(?:|[Dd]atabase )[Hh]as entity (?P<name>.+)$/
|
||||
*
|
||||
* @param string $name Entity short name like AppBundle:Entity or FQCN.
|
||||
* @param TableNode $table Search parameters in table format.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function hasEntity($name, TableNode $table)
|
||||
{
|
||||
$params = [];
|
||||
|
||||
$tableData = $table->getTable();
|
||||
foreach ($tableData as $row) {
|
||||
$params[current($row)] = next($row);
|
||||
}
|
||||
|
||||
$entity = $this->dataBaseHelper->getEntity($name, $params);
|
||||
|
||||
self::assertNotNull(
|
||||
$entity,
|
||||
"Can't find entity {$name} with parameters " . PHP_EOL
|
||||
. json_encode($params, JSON_PRETTY_PRINT)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^(?:|[Dd]atabase )[Hh]as (?P<count>\d+) entity (?P<name>.+)$/
|
||||
* @Then /?[Dd]on't has entity (?P<name>.+)$?
|
||||
*
|
||||
* @param string $name Entity short name like AppBundle:Entity or FQCN.
|
||||
* @param integer $count Expected entities count.
|
||||
* @param TableNode $table Search parameters in table format.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function hasEntities($name, $count, TableNode $table)
|
||||
{
|
||||
$params = [];
|
||||
|
||||
$tableData = $table->getTable();
|
||||
foreach ($tableData as $row) {
|
||||
$params[current($row)] = next($row);
|
||||
}
|
||||
|
||||
$entities = $this->dataBaseHelper->getEntities($name, $params);
|
||||
|
||||
self::assertCount(
|
||||
(int) $count,
|
||||
$entities,
|
||||
"Can't find {$count} entity {$name} with parameters " . PHP_EOL
|
||||
. json_encode($params, JSON_PRETTY_PRINT) .PHP_EOL .
|
||||
'Actually found: '. count($entities)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^[Ii] want to delete entity (?P<name>.+)$/
|
||||
*
|
||||
* @param string $name Entity short name like AppBundle:Entity or FQCN.
|
||||
* @param TableNode $table Search parameters in table format.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function deleteEntity($name, TableNode $table)
|
||||
{
|
||||
$params = [];
|
||||
|
||||
$tableData = $table->getTable();
|
||||
foreach ($tableData as $row) {
|
||||
$params[current($row)] = next($row);
|
||||
}
|
||||
|
||||
$this->dataBaseHelper->deleteEntity($name, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^(?:|[Dd]atabase )[Dd]on't has entity (?P<name>.+)$/
|
||||
* @Then /?[Hh]as entity (?P<name>.+)$?
|
||||
*
|
||||
* @param string $name Entity short name like AppBundle:Entity or FQCN.
|
||||
* @param TableNode $table Search parameters ikn table format.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function entityNotExists($name, TableNode $table)
|
||||
{
|
||||
$params = [];
|
||||
|
||||
$tableData = $table->getTable();
|
||||
foreach ($tableData as $row) {
|
||||
$params[current($row)] = next($row);
|
||||
}
|
||||
|
||||
$entities = $this->dataBaseHelper->getEntities($name, $params);
|
||||
|
||||
self::assertCount(
|
||||
0,
|
||||
$entities,
|
||||
"Entity {$name} with parameters " . PHP_EOL
|
||||
. json_encode($params, JSON_PRETTY_PRINT) . PHP_EOL .'exists!'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param EntityManagerInterface $em A EntityManagerInterface instance.
|
||||
* @param array $fqcnList List of available fqcn's.
|
||||
*
|
||||
* @return array|mixed
|
||||
*/
|
||||
protected function processEntityMetadata(EntityManagerInterface $em, array $fqcnList)
|
||||
{
|
||||
$entities = [];
|
||||
|
||||
foreach ($fqcnList as $fqcn) {
|
||||
$reflection = new \ReflectionClass($fqcn);
|
||||
if ($reflection->implementsInterface(NormalizableEntityInterface::class)) {
|
||||
$name = \app\c\entityFqcnToShort($fqcn);
|
||||
|
||||
if ($reflection->isAbstract()) {
|
||||
$entities[$name] = new EntityMetadata($this->processAbstractMetadata($em, $reflection));
|
||||
} else {
|
||||
/** @var NormalizableEntityInterface $entity */
|
||||
$entity = $reflection->newInstanceWithoutConstructor();
|
||||
$entities[$name] = new EntityMetadata($entity->getMetadata());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param EntityManagerInterface $em A EntityManagerInterface
|
||||
* instance.
|
||||
* @param \ReflectionClass $reflection A ReflectionClass instance.
|
||||
*
|
||||
* @return Metadata
|
||||
*/
|
||||
protected function processAbstractMetadata(
|
||||
EntityManagerInterface $em,
|
||||
\ReflectionClass $reflection
|
||||
) {
|
||||
/** @var ClassMetadataInfo $doctrineMetadata */
|
||||
$doctrineMetadata = $em->getClassMetadata($reflection->getName());
|
||||
$map = $doctrineMetadata->discriminatorMap;
|
||||
|
||||
if (! is_array($map) || (count($map) === 0)) {
|
||||
// Parsed abstract class don't has discriminator column.
|
||||
$message = 'Abstract class without discriminator column not allowed';
|
||||
throw new \InvalidArgumentException($message);
|
||||
}
|
||||
|
||||
$metadata = new Metadata($reflection->getName());
|
||||
|
||||
$metadataList = array_map(function ($fqcn) use ($em) {
|
||||
$reflection = new \ReflectionClass($fqcn);
|
||||
if ($reflection->isAbstract()) {
|
||||
/** @var ClassMetadataInfo $doctrineMetadata */
|
||||
$doctrineMetadata = $em->getClassMetadata($fqcn);
|
||||
$map = $doctrineMetadata->discriminatorMap;
|
||||
return $this->processAbstractMetadata($em, $map);
|
||||
}
|
||||
|
||||
/** @var NormalizableEntityInterface $entity */
|
||||
$entity = $reflection->newInstanceWithoutConstructor();
|
||||
return $entity->getMetadata();
|
||||
}, $map);
|
||||
|
||||
return $metadata->admixList($metadataList);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Context;
|
||||
|
||||
use Behat\Gherkin\Node\TableNode;
|
||||
use Common\Util\Index\ExternalIndexConnection;
|
||||
use Common\Util\Index\InternalIndexConnection;
|
||||
use Common\Util\Index\InternalSourceConnection;
|
||||
use Common\Util\Index\TestIndexConnectionInterface;
|
||||
|
||||
/**
|
||||
* Class IndexContextTrait
|
||||
* Contains steps definitions for working with indices.
|
||||
*
|
||||
* @package Common\Context
|
||||
*/
|
||||
trait IndexContextTrait
|
||||
{
|
||||
|
||||
/**
|
||||
* @var ExternalIndexConnection
|
||||
*/
|
||||
protected $externalIndex;
|
||||
|
||||
/**
|
||||
* @var InternalIndexConnection
|
||||
*/
|
||||
protected $internalIndex;
|
||||
|
||||
/**
|
||||
* @var InternalSourceConnection
|
||||
*/
|
||||
protected $sourceIndex;
|
||||
|
||||
/**
|
||||
* @Transform /^([Ee]xternal|[Ii]nternal|[Ss]ource)$/
|
||||
*
|
||||
* @param string $index Index name.
|
||||
*
|
||||
* @return TestIndexConnectionInterface
|
||||
*/
|
||||
public function getConnectionByName($index)
|
||||
{
|
||||
$index = strtolower($index);
|
||||
|
||||
switch ($index) {
|
||||
case 'external':
|
||||
return $this->externalIndex;
|
||||
|
||||
case 'internal':
|
||||
return $this->internalIndex;
|
||||
|
||||
case 'source':
|
||||
return $this->sourceIndex;
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException("Unknown index '{$index}'");
|
||||
}
|
||||
|
||||
/**
|
||||
* Json parameters should have only necessary fields. Over will be auto
|
||||
* generated.
|
||||
*
|
||||
* @Given /^(?:I add|[Hh]as) new document (?:in|to) (?P<connection>(external|internal|source)) index$/
|
||||
*
|
||||
* @param TestIndexConnectionInterface $index A TestIndexConnectionInterface
|
||||
* instance.
|
||||
* @param TableNode $table A TableNode instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function indexDocument(
|
||||
TestIndexConnectionInterface $index,
|
||||
TableNode $table
|
||||
) {
|
||||
/** @var AbstractContext $this */
|
||||
$document = $index->createDocument();
|
||||
|
||||
$params = [];
|
||||
$tableData = $table->getTable();
|
||||
foreach ($tableData as $row) {
|
||||
$params[current($row)] = $this->processor->process(next($row));
|
||||
}
|
||||
|
||||
foreach ($params as $name => $value) {
|
||||
$document[$name] = $value;
|
||||
}
|
||||
|
||||
$index->index($document);
|
||||
// Wait to insure that all fixtures was indexed.
|
||||
usleep(100000);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^(?P<connection>([Ee]xternal|[Ii]nternal|[Ss]ource)) index has (?P<count>\d+) document[s]?$/
|
||||
*
|
||||
* @param TestIndexConnectionInterface $connection A
|
||||
* TestIndexConnectionInterface
|
||||
* instance.
|
||||
* @param integer $count A expected documents
|
||||
* count.
|
||||
* @param TableNode $table A TableNode instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function hasDocuments(
|
||||
TestIndexConnectionInterface $connection,
|
||||
$count,
|
||||
TableNode $table
|
||||
) {
|
||||
/** @var AbstractContext $this */
|
||||
$tableData = $table->getTable();
|
||||
$factory = $connection->getFilterFactory();
|
||||
$filters = [];
|
||||
foreach ($tableData as $row) {
|
||||
$field = current($row);
|
||||
$type = next($row);
|
||||
$value = next($row);
|
||||
|
||||
if ($type === 'in') {
|
||||
$value = array_filter(array_map('trim', explode(',', $value)));
|
||||
}
|
||||
|
||||
$filters[] =
|
||||
$factory->{$type}($field, $this->processor->process($value));
|
||||
}
|
||||
|
||||
$results = $connection->createRequestBuilder()
|
||||
->setFilters($filters)
|
||||
->build()
|
||||
->execute();
|
||||
|
||||
self::assertCount((int) $count, $results);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Util\Converter;
|
||||
|
||||
/**
|
||||
* Class DateConverter
|
||||
* @package Common\Util\Converter
|
||||
*/
|
||||
class DateConverter
|
||||
{
|
||||
|
||||
/**
|
||||
* Map between date format and proper regular patterns.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private static $datePatternsMap = [
|
||||
'Y-m-d' => '\d{4}-\d{2}-\d{2}',
|
||||
'Y-m-d H:i:s' => '\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}',
|
||||
'Y-m-d\TH:i:sP' => '\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(:?(:?\+|\-)\d{2}:\d{2}|\w)',
|
||||
];
|
||||
|
||||
/**
|
||||
* Cache of formats for specified dates.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private static $dateFormatCache = [];
|
||||
|
||||
/**
|
||||
* Check that specified string can be converted to \DateTime instance.
|
||||
*
|
||||
* @param string $date Date string.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function can($date)
|
||||
{
|
||||
return self::getFormat($date) !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $date Date string.
|
||||
*
|
||||
* @return \DateTime|false
|
||||
*/
|
||||
public static function convert($date)
|
||||
{
|
||||
$format = self::getFormat($date);
|
||||
|
||||
if ($format === null) {
|
||||
throw new \InvalidArgumentException('Invalid date '. $date);
|
||||
}
|
||||
|
||||
return date_create_from_format($format, $date);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get proper date format for specified date string.
|
||||
*
|
||||
* @param string $date Date string.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
private static function getFormat($date)
|
||||
{
|
||||
if (! is_string($date)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! isset(self::$dateFormatCache[$date])) {
|
||||
self::$dateFormatCache[$date] = null;
|
||||
|
||||
// Check specified date against all available patterns and find
|
||||
// proper format.
|
||||
foreach (self::$datePatternsMap as $format => $pattern) {
|
||||
if ((preg_match('/^' . $pattern . '$/', $date) === 1)
|
||||
&& (date_create_from_format($format, $date) !== false)) {
|
||||
self::$dateFormatCache[$date] = $format;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self::$dateFormatCache[$date];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Util;
|
||||
|
||||
use Common\Util\Converter\DateConverter;
|
||||
use Doctrine\Bundle\DoctrineBundle\Registry;
|
||||
|
||||
/**
|
||||
* Class DatabaseHelper
|
||||
* Check database status.
|
||||
*
|
||||
* @package Common\Util
|
||||
*/
|
||||
class DatabaseHelper
|
||||
{
|
||||
|
||||
/**
|
||||
* @var Registry
|
||||
*/
|
||||
private $registry;
|
||||
|
||||
/**
|
||||
* DatabaseHelper constructor.
|
||||
*
|
||||
* @param Registry $registry A Registry instance.
|
||||
*/
|
||||
public function __construct(Registry $registry)
|
||||
{
|
||||
$this->registry = $registry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that given entity with specified parameters are exists in our
|
||||
* database.
|
||||
*
|
||||
* @param string $name Entity short name like AppBundle:Entity or FQCN.
|
||||
* @param array $params Entity parameters.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getEntities($name, array $params)
|
||||
{
|
||||
// Process parameters.
|
||||
$params = $this->parseParams($params);
|
||||
|
||||
return $this->registry->getRepository($name)->findBy($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove some entities from BD by parameters
|
||||
*
|
||||
* @param string $name Entity short name like AppBundle:Entity or FQCN.
|
||||
* @param array $params Entity parameters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function deleteEntity($name, array $params)
|
||||
{
|
||||
// Process parameters.
|
||||
$params = $this->parseParams($params);
|
||||
|
||||
$entities = $this->registry->getRepository($name)->findBy($params);
|
||||
$em = $this->registry->getEntityManager();
|
||||
foreach ($entities as $entity) {
|
||||
$em->remove($entity);
|
||||
}
|
||||
$em->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that given entity with specified parameters are exists in our
|
||||
* database.
|
||||
*
|
||||
* @param string $name Entity short name like AppBundle:Entity or FQCN.
|
||||
* @param array $params Entity parameters.
|
||||
*
|
||||
* @return object|null
|
||||
*/
|
||||
public function getEntity($name, array $params)
|
||||
{
|
||||
// Process parameters.
|
||||
$params = $this->parseParams($params);
|
||||
|
||||
return $this->registry->getRepository($name)->findOneBy($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse raw parameters.
|
||||
*
|
||||
* @param array $params Entity parameters.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function parseParams(array $params)
|
||||
{
|
||||
return array_map(function ($parameter) {
|
||||
$origin = trim($parameter);
|
||||
$buf = strtolower($origin);
|
||||
|
||||
switch (true) {
|
||||
// Parameter is valid numerical value convert it to float or
|
||||
// integer.
|
||||
case is_numeric($buf):
|
||||
if (strpos($buf, '.') !== false) {
|
||||
return (float) $buf;
|
||||
}
|
||||
|
||||
return (int) $buf;
|
||||
|
||||
case DateConverter::can($buf):
|
||||
return DateConverter::convert($buf);
|
||||
|
||||
case in_array($origin, \DateTimeZone::listIdentifiers(), true):
|
||||
return new \DateTimeZone($origin);
|
||||
|
||||
// Parameter contains boolean value.
|
||||
case ($buf === 'true') || ($buf === 'false'):
|
||||
return $buf === 'true';
|
||||
}
|
||||
|
||||
return $parameter;
|
||||
}, $params);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Util\Index;
|
||||
|
||||
use AppBundle\AdvancedFilters\AFResolverInterface;
|
||||
use AppBundle\Response\SearchResponseInterface;
|
||||
use IndexBundle\Aggregation\AggregationFacadeInterface;
|
||||
use IndexBundle\Aggregation\Factory\AggregationFactoryInterface;
|
||||
use IndexBundle\Index\External\ExternalIndexInterface;
|
||||
use IndexBundle\Index\IndexInterface;
|
||||
use IndexBundle\Index\Internal\InternalIndexInterface;
|
||||
use IndexBundle\Index\Source\SourceIndexInterface;
|
||||
use IndexBundle\Index\Strategy\IndexStrategyInterface;
|
||||
use IndexBundle\Model\DocumentInterface;
|
||||
use IndexBundle\SearchRequest\SearchRequestBuilderInterface;
|
||||
use IndexBundle\SearchRequest\SearchRequestInterface;
|
||||
|
||||
/**
|
||||
* Class AbstractTestIndexConnection
|
||||
*
|
||||
* @package Common\Util\Index
|
||||
*/
|
||||
abstract class AbstractTestIndexConnection implements TestIndexConnectionInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var InternalIndexInterface
|
||||
*/
|
||||
private $index;
|
||||
|
||||
/**
|
||||
* AbstractIndexConnection constructor.
|
||||
*
|
||||
* @param InternalIndexInterface $index A InternalIndexInterface interface.
|
||||
*/
|
||||
public function __construct(InternalIndexInterface $index)
|
||||
{
|
||||
$this->index = $index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update specified document.
|
||||
*
|
||||
* Make partial update so in data must be placed only changed properties.
|
||||
*
|
||||
* @param string|integer $id Updated document id.
|
||||
* @param array $data Array of changed data where key is property
|
||||
* name and value is new property value.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function update($id, array $data)
|
||||
{
|
||||
$this->index->update($id, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update array of documents.
|
||||
*
|
||||
* Make partial update so for each document id we should place only changed
|
||||
* property.
|
||||
*
|
||||
* @param array $config Array of arrays where key is updated document id and
|
||||
* value is array of updated fields same as $data in
|
||||
* `update` method.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function updateBulk(array $config)
|
||||
{
|
||||
$this->index->updateBulk($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update array of documents with filtering.
|
||||
*
|
||||
* Make partial update so for each document id we should place only changed
|
||||
* property.
|
||||
*
|
||||
* @param SearchRequestInterface $request A SearchRequestInterface instance.
|
||||
* @param string $script Updating script.
|
||||
* @param array $params Script parameters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function updateByQuery(SearchRequestInterface $request, $script, array $params = [])
|
||||
{
|
||||
$this->index->updateByQuery($request, $script, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove document by specified id or array of ids.
|
||||
*
|
||||
* @param string|string[] $id Document id or array of document ids.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function remove($id)
|
||||
{
|
||||
$this->index->remove($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search information in index.
|
||||
*
|
||||
* @param SearchRequestInterface $request Internal representation of search
|
||||
* request.
|
||||
*
|
||||
* @return SearchResponseInterface
|
||||
*/
|
||||
public function search(SearchRequestInterface $request)
|
||||
{
|
||||
return $this->index->search($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all relevant documents.
|
||||
*
|
||||
* @param SearchRequestInterface $request Internal representation of search
|
||||
* request.
|
||||
*
|
||||
* @return \Traversable
|
||||
*/
|
||||
public function fetchAll(SearchRequestInterface $request)
|
||||
{
|
||||
return $this->index->fetchAll($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create search request builder for this index connection.
|
||||
*
|
||||
* @return SearchRequestBuilderInterface
|
||||
*/
|
||||
public function createRequestBuilder()
|
||||
{
|
||||
return $this->index->createRequestBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get filter factory instance.
|
||||
*
|
||||
* @return \IndexBundle\Filter\Factory\FilterFactoryInterface
|
||||
*/
|
||||
public function getFilterFactory()
|
||||
{
|
||||
return $this->index->getFilterFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get aggregation factory instance
|
||||
*
|
||||
* @return AggregationFactoryInterface
|
||||
*/
|
||||
public function getAggregationFactory()
|
||||
{
|
||||
return $this->index->getAggregationFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get aggregation instance
|
||||
*
|
||||
* @return AggregationFacadeInterface
|
||||
*/
|
||||
public function getAggregation()
|
||||
{
|
||||
return $this->index->getAggregation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return advanced filters aggregator.
|
||||
*
|
||||
* @return AFResolverInterface
|
||||
*/
|
||||
public function getAFResolver()
|
||||
{
|
||||
return $this->index->getAFResolver();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get strategy used by this index.
|
||||
*
|
||||
* @return IndexStrategyInterface
|
||||
*/
|
||||
public function getStrategy()
|
||||
{
|
||||
return $this->index->getStrategy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new index.
|
||||
*
|
||||
* @param array $mapping Index mapping.
|
||||
* @param array $settings Index settings.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function createIndex(array $mapping, array $settings = [])
|
||||
{
|
||||
if ($this->index instanceof InternalIndexInterface) {
|
||||
$this->index->createIndex($mapping, $settings);
|
||||
} else {
|
||||
throw new \LogicException('Can\'t create index on '. get_class($this->index));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Index given document or array of documents.
|
||||
*
|
||||
* @param DocumentInterface|DocumentInterface[] $data DocumentInterface instance
|
||||
* or array of instances.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function index($data)
|
||||
{
|
||||
if ($this->index instanceof InternalIndexInterface) {
|
||||
$this->index->index($data);
|
||||
} else {
|
||||
throw new \LogicException('Can\'t index documents on '. get_class($this->index));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge index.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function purge()
|
||||
{
|
||||
if ($this->index instanceof InternalIndexInterface) {
|
||||
$this->index->purge();
|
||||
} else {
|
||||
throw new \LogicException('Can\'t purge index on '. get_class($this->index));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get documents by it ids.
|
||||
*
|
||||
* @param integer|integer[] $ids Array of document ids or single id.
|
||||
* @param string|string[] $fields Array of requested fields of single
|
||||
* field.
|
||||
*
|
||||
* @return \IndexBundle\Model\DocumentInterface[]
|
||||
*/
|
||||
public function get($ids, $fields = [])
|
||||
{
|
||||
return $this->index->get($ids, $fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that specified documents is exists.
|
||||
*
|
||||
* @param integer|array $ids Array of document ids or single id.
|
||||
*
|
||||
* @return array Contains all ids which not found in index.
|
||||
*/
|
||||
public function has($ids)
|
||||
{
|
||||
return $this->index->has($ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return IndexInterface|InternalIndexInterface|ExternalIndexInterface|SourceIndexInterface
|
||||
*/
|
||||
public function getIndex()
|
||||
{
|
||||
return $this->index;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Util\Index;
|
||||
|
||||
use IndexBundle\Index\External\ExternalIndexInterface;
|
||||
use IndexBundle\Model\Generator\ExternalDocumentGenerator;
|
||||
use IndexBundle\Model\DocumentInterface;
|
||||
use IndexBundle\Util\Initializer\ExternalIndexInitializer;
|
||||
|
||||
/**
|
||||
* Class ExternalIndexConnection
|
||||
*
|
||||
* @package Common\Util\Index
|
||||
*/
|
||||
class ExternalIndexConnection extends AbstractTestIndexConnection
|
||||
{
|
||||
|
||||
/**
|
||||
* @var ExternalDocumentGenerator
|
||||
*/
|
||||
private $documentGenerator;
|
||||
|
||||
/**
|
||||
* ExternalIndexConnection constructor.
|
||||
*
|
||||
* @param ExternalIndexInterface $index A ExternalIndexInterface interface.
|
||||
*/
|
||||
public function __construct(ExternalIndexInterface $index)
|
||||
{
|
||||
parent::__construct($index);
|
||||
$this->documentGenerator = new ExternalDocumentGenerator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup external index.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setup()
|
||||
{
|
||||
ExternalIndexInitializer::initialize($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new document for this index.
|
||||
*
|
||||
* @return DocumentInterface
|
||||
*/
|
||||
public function createDocument()
|
||||
{
|
||||
return $this->documentGenerator->generate();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Util\Index;
|
||||
|
||||
use IndexBundle\Index\Internal\InternalIndexInterface;
|
||||
use IndexBundle\Model\Generator\InternalDocumentGenerator;
|
||||
use IndexBundle\Model\DocumentInterface;
|
||||
use IndexBundle\Util\Initializer\InternalIndexInitializer;
|
||||
|
||||
/**
|
||||
* Class InternalIndexConnection
|
||||
* @package Common\Util\Index
|
||||
*/
|
||||
class InternalIndexConnection extends AbstractTestIndexConnection
|
||||
{
|
||||
|
||||
/**
|
||||
* @var InternalDocumentGenerator
|
||||
*/
|
||||
private $documentGenerator;
|
||||
|
||||
/**
|
||||
* ExternalIndexConnection constructor.
|
||||
*
|
||||
* @param InternalIndexInterface $index A InternalIndexInterface interface.
|
||||
*/
|
||||
public function __construct(InternalIndexInterface $index)
|
||||
{
|
||||
parent::__construct($index);
|
||||
$this->documentGenerator = new InternalDocumentGenerator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup internal index.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setup()
|
||||
{
|
||||
InternalIndexInitializer::initialize($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new document for this index.
|
||||
*
|
||||
* @return DocumentInterface
|
||||
*/
|
||||
public function createDocument()
|
||||
{
|
||||
return $this->documentGenerator->generate();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Util\Index;
|
||||
|
||||
use IndexBundle\Index\Source\SourceIndexInterface;
|
||||
use IndexBundle\Model\Generator\SourceDocumentGenerator;
|
||||
use IndexBundle\Model\DocumentInterface;
|
||||
use IndexBundle\Util\Initializer\SourceIndexInitializer;
|
||||
|
||||
/**
|
||||
* Class InternalSourceConnection
|
||||
* @package Common\Util\Index
|
||||
*/
|
||||
class InternalSourceConnection extends AbstractTestIndexConnection implements SourceIndexInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var SourceDocumentGenerator
|
||||
*/
|
||||
private $documentGenerator;
|
||||
|
||||
/**
|
||||
* ExternalIndexConnection constructor.
|
||||
*
|
||||
* @param SourceIndexInterface $index A SourceIndexInterface interface.
|
||||
*/
|
||||
public function __construct(SourceIndexInterface $index)
|
||||
{
|
||||
parent::__construct($index);
|
||||
$this->documentGenerator = new SourceDocumentGenerator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup internal index.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setup()
|
||||
{
|
||||
SourceIndexInitializer::initialize($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new document for this index.
|
||||
*
|
||||
* @return DocumentInterface
|
||||
*/
|
||||
public function createDocument()
|
||||
{
|
||||
return $this->documentGenerator->generate();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Util\Index;
|
||||
|
||||
use IndexBundle\Index\External\ExternalIndexInterface;
|
||||
use IndexBundle\Index\IndexInterface;
|
||||
use IndexBundle\Index\Internal\InternalIndexInterface;
|
||||
use IndexBundle\Index\Source\SourceIndexInterface;
|
||||
use IndexBundle\Model\DocumentInterface;
|
||||
|
||||
/**
|
||||
* Interface TestIndexConnectionInterface
|
||||
*
|
||||
* @package Common\Util\Index
|
||||
*/
|
||||
interface TestIndexConnectionInterface extends InternalIndexInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Setup index with mappings.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setup();
|
||||
|
||||
/**
|
||||
* Create new document for this index.
|
||||
*
|
||||
* @return DocumentInterface
|
||||
*/
|
||||
public function createDocument();
|
||||
|
||||
/**
|
||||
* Create new index.
|
||||
*
|
||||
* @param array $mapping Index mapping.
|
||||
* @param array $settings Index settings.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function createIndex(array $mapping, array $settings = []);
|
||||
|
||||
/**
|
||||
* Index given document or array of documents.
|
||||
*
|
||||
* @param DocumentInterface|DocumentInterface[] $data DocumentInterface instance
|
||||
* or array of instances.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function index($data);
|
||||
|
||||
/**
|
||||
* Purge index.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function purge();
|
||||
|
||||
/**
|
||||
* @return IndexInterface|InternalIndexInterface|ExternalIndexInterface|SourceIndexInterface
|
||||
*/
|
||||
public function getIndex();
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Util\Matcher;
|
||||
|
||||
/**
|
||||
* Class AppMatcher
|
||||
* Facade for matching process.
|
||||
*
|
||||
* @package Common\Util\Matcher
|
||||
*/
|
||||
class AppMatcher
|
||||
{
|
||||
|
||||
/**
|
||||
* Array of entities metadata.
|
||||
* Make static because for some reason ExpanderInitializer from
|
||||
* coduo/php-Matcher mark as final and haven't interface, so we can't override
|
||||
* or decorate it and pass this entities into it. Therefore we can't pass
|
||||
* this map into concrete expander ... :-(
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $entities;
|
||||
|
||||
/**
|
||||
* @param array $entities Array of entities pattern.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function registerEntities(array $entities = [])
|
||||
{
|
||||
self::$entities = $entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $entityName Entity name.
|
||||
*
|
||||
* @return \Common\Util\Metadata\EntityMetadata
|
||||
*/
|
||||
public static function getEntityMetadata($entityName)
|
||||
{
|
||||
return self::$entities[$entityName];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $value Checked value.
|
||||
* @param string $pattern Pattern.
|
||||
* @param null|string $error Error.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function match($value, $pattern, &$error = null)
|
||||
{
|
||||
$factory = new MatcherFactory();
|
||||
$matcher = $factory->createMatcher();
|
||||
|
||||
if (! $matcher->match($value, $pattern)) {
|
||||
$error = $matcher->getError();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Util\Matcher\Expander;
|
||||
|
||||
use Coduo\PHPMatcher\Matcher\Pattern\PatternExpander;
|
||||
|
||||
/**
|
||||
* Class AbstractChainExpander
|
||||
* @package Common\Util\Matcher\Expander
|
||||
*/
|
||||
abstract class AbstractChainExpander extends AbstractExpander
|
||||
{
|
||||
|
||||
/**
|
||||
* @var PatternExpander[]
|
||||
*/
|
||||
protected $expanders = [];
|
||||
|
||||
/**
|
||||
* @param PatternExpander $expander A PatternExpander instance.
|
||||
* @param PatternExpander $expander,... A PatternExpander's instances.
|
||||
*/
|
||||
public function __construct(PatternExpander $expander)
|
||||
{
|
||||
$this->expanders[] = $expander;
|
||||
|
||||
if (func_num_args() > 1) {
|
||||
$arguments = func_get_args();
|
||||
$length = count($arguments);
|
||||
for ($i = 1; $i < $length; ++$i) {
|
||||
if (!$arguments[$i] instanceof PatternExpander) {
|
||||
throw new \InvalidArgumentException('Has invalid expander.');
|
||||
}
|
||||
|
||||
$this->expanders[] = $arguments[$i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value Value to match.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function match($value)
|
||||
{
|
||||
foreach ($this->expanders as $expander) {
|
||||
if (! $expander->match($value)) {
|
||||
$className = get_class($expander);
|
||||
$className = substr($className, strrpos($className, '\\'));
|
||||
|
||||
$this->error = "Expander {$className} don't matches value: ".
|
||||
$expander->getError();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Util\Matcher\Expander;
|
||||
|
||||
use Coduo\PHPMatcher\Matcher\Pattern\PatternExpander;
|
||||
|
||||
/**
|
||||
* Class AbstractExpander
|
||||
* @package Common\Util\Matcher\Expander
|
||||
*/
|
||||
abstract class AbstractExpander implements PatternExpander
|
||||
{
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected $error;
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getError()
|
||||
{
|
||||
return $this->error;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Util\Matcher\Expander;
|
||||
|
||||
use Common\Util\Converter\DateConverter;
|
||||
|
||||
/**
|
||||
* Class BetweenExpander
|
||||
* Check that value is between specified bounds.
|
||||
* Except integers, float and datetime values.
|
||||
*
|
||||
* Example:
|
||||
* - integer.between(10, 20)
|
||||
* - date.between('2017-10-01', '2017-11-01')
|
||||
* - .field('date', between('2017-10-01', '2017-11-01'))
|
||||
*
|
||||
* @package Common\Util\Matcher\Expander
|
||||
*/
|
||||
class BetweenExpander extends AbstractExpander
|
||||
{
|
||||
|
||||
/**
|
||||
* @var integer|float|string
|
||||
*/
|
||||
private $start;
|
||||
|
||||
/**
|
||||
* @var integer|float|string
|
||||
*/
|
||||
private $end;
|
||||
|
||||
/**
|
||||
* @param integer|float|string $start Start bound.
|
||||
* @param integer|float|string $end End bound.
|
||||
*/
|
||||
public function __construct($start, $end)
|
||||
{
|
||||
$this->start = $start;
|
||||
$this->end = $end;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value Value to match.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function match($value)
|
||||
{
|
||||
if (! is_numeric($value) && ! is_int($value) && ! is_float($value)
|
||||
&& ! is_string($value)) {
|
||||
$this->error = 'Can match only integers, float and datetime values';
|
||||
return false;
|
||||
}
|
||||
|
||||
$start = $this->start;
|
||||
$end = $this->end;
|
||||
if (DateConverter::can($value)) {
|
||||
// For string which represent date try to convert it into \DateTime
|
||||
// instances.
|
||||
try {
|
||||
$value = DateConverter::convert($value);
|
||||
$start = DateConverter::convert($start);
|
||||
$end = DateConverter::convert($end);
|
||||
|
||||
$value->setTimezone($start->getTimezone());
|
||||
} catch (\Exception $e) {
|
||||
$this->error = $e->getMessage();
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// For scalar types convert all values to the same type.
|
||||
$type = gettype($start);
|
||||
settype($value, $type);
|
||||
}
|
||||
|
||||
return ($value >= $start) && ($value <= $end);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Util\Matcher\Expander;
|
||||
|
||||
use Common\Util\Matcher\AppMatcher;
|
||||
|
||||
/**
|
||||
* Class EntityExpander
|
||||
* Check that expanded object is serialized application entity.
|
||||
*
|
||||
* Example: object.entity('AppBundle:User', 'user, post')
|
||||
*
|
||||
* @package Util\Matcher\Expander
|
||||
*/
|
||||
class EntityExpander extends AbstractExpander
|
||||
{
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $entityName;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $serializationGroups;
|
||||
|
||||
/**
|
||||
* @param string $entityName Entity name like
|
||||
* 'BundleName:EntityName'.
|
||||
* @param string|array $serializationGroups Serialization groups in string
|
||||
* format delimited by ','.
|
||||
*/
|
||||
public function __construct($entityName, $serializationGroups = [])
|
||||
{
|
||||
$this->entityName = $entityName;
|
||||
|
||||
// Split serialization groups string into array.
|
||||
$serializationGroups = explode(',', $serializationGroups);
|
||||
// Trim values and remove empty.
|
||||
$serializationGroups = array_filter(
|
||||
array_map('trim', $serializationGroups)
|
||||
);
|
||||
|
||||
$this->serializationGroups = $serializationGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value Value to match.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function match($value)
|
||||
{
|
||||
// Get entity metadata for specified entity.
|
||||
$metadata = AppMatcher::getEntityMetadata($this->entityName);
|
||||
// Get patter fot specified entity with given serialization group.
|
||||
$pattern = $metadata->getPattern($this->serializationGroups);
|
||||
|
||||
if ($pattern && ! AppMatcher::match($value, $pattern, $this->error)) {
|
||||
$this->error =
|
||||
"Invalid entity {$this->entityName}: {$this->error}";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Util\Matcher\Expander;
|
||||
|
||||
use Coduo\ToString\StringConverter;
|
||||
|
||||
/**
|
||||
* Class EveryExpander
|
||||
* Check that every array elements matches specified expander.
|
||||
*
|
||||
* Example:
|
||||
* - array.every(field('property', value))
|
||||
* - array.every(entity('AppBundle:User', 'user, post'))
|
||||
*
|
||||
* @package Common\Util\Matcher\Expander
|
||||
*/
|
||||
class EveryExpander extends AbstractChainExpander
|
||||
{
|
||||
|
||||
/**
|
||||
* @param mixed $value Value to match.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function match($value)
|
||||
{
|
||||
if (! is_array($value)) {
|
||||
$this->error = 'Every expander require "array", got '.
|
||||
new StringConverter($value) .'.';
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($value as $row) {
|
||||
if (! parent::match($row)) {
|
||||
$this->error = 'Checked value '. new StringConverter($row)
|
||||
.' is invalid: '. $this->error;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Util\Matcher\Expander;
|
||||
|
||||
use Coduo\PHPMatcher\Matcher\Pattern\PatternExpander;
|
||||
|
||||
/**
|
||||
* Class FieldExpander
|
||||
* Check that expanded object or array has specific field which matched
|
||||
* given matcher expander.
|
||||
*
|
||||
* Example:
|
||||
* - .field('username', contains('admin'))
|
||||
* - .field('user', entity('AppBundle:User', 'user'), field('id', 1))
|
||||
*
|
||||
* @package Common\Util\Matcher\Expander
|
||||
*/
|
||||
class FieldExpander extends AbstractExpander
|
||||
{
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $fieldName;
|
||||
|
||||
/**
|
||||
* @var mixed|PatternExpander[]
|
||||
*/
|
||||
private $expander;
|
||||
|
||||
/**
|
||||
* @param string $fieldName Field name.
|
||||
* @param mixed|PatternExpander $expander A PatternExpander instance.
|
||||
* @param PatternExpander $expander,... A PatternExpander's instances.
|
||||
*/
|
||||
public function __construct($fieldName, $expander)
|
||||
{
|
||||
$this->fieldName = $fieldName;
|
||||
$this->expander = $expander;
|
||||
|
||||
if ($expander instanceof PatternExpander) {
|
||||
$expander = func_get_args();
|
||||
$length = count($expander);
|
||||
// Process all except first argument which contains field name.
|
||||
$this->expander = [];
|
||||
for ($i = 1; $i < $length; ++$i) {
|
||||
if (!$expander[$i] instanceof PatternExpander) {
|
||||
throw new \InvalidArgumentException('Has invalid expander.');
|
||||
}
|
||||
|
||||
$this->expander[] = $expander[$i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value Value to match.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function match($value)
|
||||
{
|
||||
if (! is_array($value) && !isset($value[$this->fieldName])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_array($this->expander)) {
|
||||
// Match all expanders.
|
||||
foreach ($this->expander as $expander) {
|
||||
if (! $expander->match($value[$this->fieldName])) {
|
||||
$this->error = "Field {$this->fieldName}: expander don't matches value. ".
|
||||
$expander->getError();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// All expanders successfully matches.
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($value[$this->fieldName] !== $this->expander) {
|
||||
$this->error = "Field {$this->fieldName}: don't equal to {$this->expander}";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Util\Matcher\Expander;
|
||||
|
||||
use Common\Util\Converter\DateConverter;
|
||||
|
||||
/**
|
||||
* Class GteExpander
|
||||
* Check that value is greater or equal to another value.
|
||||
* Except integers, float and datetime values.
|
||||
*
|
||||
* Example:
|
||||
* - integer.gte(10)
|
||||
* - date.gte('2017-10-01')
|
||||
* - .field('date', gte('2017-10-01'))
|
||||
*
|
||||
* @package Common\Util\Matcher\Expander
|
||||
*/
|
||||
class GteExpander extends AbstractExpander
|
||||
{
|
||||
|
||||
/**
|
||||
* @var integer|string|\DateTimeInterface|float
|
||||
*/
|
||||
private $value;
|
||||
|
||||
/**
|
||||
* @param integer|string|\DateTimeInterface|float $value Expected value.
|
||||
*/
|
||||
public function __construct($value)
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value Value to match.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function match($value)
|
||||
{
|
||||
if (! is_numeric($value) && ! is_int($value) && ! is_float($value)
|
||||
&& ! is_string($value)) {
|
||||
$this->error = 'Can match only integers, float and datetime values';
|
||||
return false;
|
||||
}
|
||||
|
||||
$bound = $this->value;
|
||||
if (DateConverter::can($value)) {
|
||||
// For string which represent date try to convert it into \DateTime
|
||||
// instances.
|
||||
try {
|
||||
$value = DateConverter::convert($value);
|
||||
$bound = DateConverter::convert($bound);
|
||||
|
||||
$bound->setTimezone($value->getTimezone());
|
||||
} catch (\Exception $e) {
|
||||
$this->error = $e->getMessage();
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// For scalar types convert all values to the same type.
|
||||
$type = gettype($bound);
|
||||
settype($value, $type);
|
||||
}
|
||||
|
||||
if (! ($matched = $value >= $bound)) {
|
||||
if ($value instanceof \DateTime) {
|
||||
$value = $value->format('c');
|
||||
$bound = $bound->format('c');
|
||||
}
|
||||
|
||||
$this->error = "Checked value {$value} less than {$bound}.";
|
||||
}
|
||||
|
||||
return $matched;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Util\Matcher\Expander;
|
||||
|
||||
use Coduo\PHPMatcher\Matcher\Pattern\PatternExpander;
|
||||
|
||||
/**
|
||||
* Class LengthExpander
|
||||
* Check that expanded array contains specified number of elements.
|
||||
*
|
||||
* Example: array.length(2)
|
||||
*
|
||||
* @package Common\Util\Matcher\Expander
|
||||
*/
|
||||
class LengthExpander extends AbstractExpander
|
||||
{
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
private $count;
|
||||
|
||||
/**
|
||||
* @param PatternExpander|integer $count Expected number of elements or
|
||||
* appropriate pattern expander.
|
||||
*/
|
||||
public function __construct($count)
|
||||
{
|
||||
if (! $count instanceof PatternExpander) {
|
||||
$count = (int) $count;
|
||||
}
|
||||
$this->count = $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value Value to match.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function match($value)
|
||||
{
|
||||
if (! is_array($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->count instanceof PatternExpander) {
|
||||
return $this->count->match(count($value));
|
||||
}
|
||||
|
||||
return count($value) === $this->count;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Util\Matcher\Expander;
|
||||
|
||||
use Coduo\PHPMatcher\Matcher\Pattern\PatternExpander;
|
||||
|
||||
/**
|
||||
* Class FieldExpander
|
||||
* Check that inner expander not match.
|
||||
*
|
||||
* Example: .not(contains('some'))
|
||||
*
|
||||
* @package Common\Util\Matcher\Expander
|
||||
*/
|
||||
class NotExpander extends AbstractExpander
|
||||
{
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
private $expander;
|
||||
|
||||
/**
|
||||
* @param PatternExpander $expander A PatternExpander instance.
|
||||
*/
|
||||
public function __construct(PatternExpander $expander)
|
||||
{
|
||||
$this->expander = $expander;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value Value to match.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function match($value)
|
||||
{
|
||||
if ($this->expander->match($value)) {
|
||||
$this->error = 'Expander match this value.';
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Util\Matcher\Expander;
|
||||
|
||||
use Coduo\ToString\StringConverter;
|
||||
|
||||
/**
|
||||
* Class OneExpander
|
||||
* Check that only one array element matches specified expander.
|
||||
*
|
||||
* Example:
|
||||
* - array.one(field('property', value))
|
||||
* - array.one(entity('AppBundle:User'), field('id', 1))
|
||||
*
|
||||
* @package Common\Util\Matcher\Expander
|
||||
*/
|
||||
class OneExpander extends AbstractChainExpander
|
||||
{
|
||||
/**
|
||||
* @param mixed $value Value to match.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function match($value)
|
||||
{
|
||||
if (! is_array($value)) {
|
||||
$this->error = 'One expander require "array", got '.
|
||||
new StringConverter($value) .'.';
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($value as $row) {
|
||||
if (parent::match($row)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Util\Matcher\Expander;
|
||||
|
||||
use Coduo\ToString\StringConverter;
|
||||
|
||||
/**
|
||||
* Class SomeExpander
|
||||
* Check that some array elements matches specified expander.
|
||||
*
|
||||
* Example:
|
||||
* - array.some(field('property', value))
|
||||
* - array.some(entity('AppBundle:User'), field('id', 1))
|
||||
*
|
||||
* @package Common\Util\Matcher\Expander
|
||||
*/
|
||||
class SomeExpander extends AbstractChainExpander
|
||||
{
|
||||
|
||||
/**
|
||||
* @param mixed $value Value to match.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function match($value)
|
||||
{
|
||||
if (! is_array($value)) {
|
||||
$this->error = 'Some expander require "array", got '.
|
||||
new StringConverter($value) .'.';
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($value as $row) {
|
||||
if (parent::match($row)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$this->error = 'No element does not match specified expanders';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Util\Matcher\Expander;
|
||||
|
||||
/**
|
||||
* Class TypeExpander
|
||||
* Check that value has specified type.
|
||||
*
|
||||
* Example:
|
||||
* - wildcart.oneOf(isEmpty(), type('double'))
|
||||
* - wildcart.type('string')
|
||||
*
|
||||
* @package Common\Util\Matcher\Expander
|
||||
*/
|
||||
class TypeExpander extends AbstractExpander
|
||||
{
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
private $type;
|
||||
|
||||
/**
|
||||
* @param string $type Expected value type.
|
||||
*/
|
||||
public function __construct($type)
|
||||
{
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value Value to match.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function match($value)
|
||||
{
|
||||
$valueType = gettype($value);
|
||||
|
||||
if ($valueType === 'float') {
|
||||
$valueType = 'double';
|
||||
}
|
||||
|
||||
return $valueType === $this->type;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Util\Matcher\Matcher;
|
||||
|
||||
use Coduo\PHPMatcher\Matcher\Matcher;
|
||||
use Coduo\PHPMatcher\Matcher\Pattern\Assert\Json;
|
||||
use Coduo\PHPMatcher\Matcher\ValueMatcher;
|
||||
|
||||
/**
|
||||
* Class JsonMatcher
|
||||
* @package Common\Util\Matcher\Matcher
|
||||
*/
|
||||
class JsonMatcher extends Matcher
|
||||
{
|
||||
/**
|
||||
* @var
|
||||
*/
|
||||
private $matcher;
|
||||
|
||||
/**
|
||||
* @param ValueMatcher $matcher
|
||||
*/
|
||||
public function __construct(ValueMatcher $matcher)
|
||||
{
|
||||
$this->matcher = $matcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if matcher can match the pattern
|
||||
*
|
||||
* @param mixed $pattern Pattern.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function canMatch($pattern)
|
||||
{
|
||||
return Json::isValidPattern($pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches value against the pattern
|
||||
*
|
||||
* @param mixed $value Checked value.
|
||||
* @param mixed $pattern Pattern.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function match($value, $pattern)
|
||||
{
|
||||
if (parent::match($value, $pattern)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Json::isValid($value)) {
|
||||
$this->error = sprintf("Invalid given JSON of value. %s", $this->getErrorMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Json::isValidPattern($pattern) ) {
|
||||
$this->error = sprintf("Invalid given JSON of pattern. %s", $this->getErrorMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
$transformedPattern = Json::transformPattern($pattern);
|
||||
$match = $this->matcher->match(json_decode($value, true), json_decode($transformedPattern, true));
|
||||
if (!$match) {
|
||||
$this->error = $this->matcher->getError();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
private function getErrorMessage()
|
||||
{
|
||||
switch(json_last_error()) {
|
||||
case JSON_ERROR_DEPTH:
|
||||
return 'Maximum stack depth exceeded';
|
||||
case JSON_ERROR_STATE_MISMATCH:
|
||||
return 'Underflow or the modes mismatch';
|
||||
case JSON_ERROR_CTRL_CHAR:
|
||||
return 'Unexpected control character found';
|
||||
case JSON_ERROR_SYNTAX:
|
||||
return 'Syntax error, malformed JSON';
|
||||
case JSON_ERROR_UTF8:
|
||||
return 'Malformed UTF-8 characters, possibly incorrectly encoded';
|
||||
default:
|
||||
return 'Unknown error';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Util\Matcher\Matcher;
|
||||
|
||||
use Coduo\PHPMatcher\Matcher\Matcher;
|
||||
use Coduo\PHPMatcher\Parser;
|
||||
use Seld\JsonLint\JsonParser;
|
||||
|
||||
/**
|
||||
* Class ObjectMatcher
|
||||
* Add 'object' pattern.
|
||||
* Used by object expander's which makes all useful tests.
|
||||
*
|
||||
* @package Common\Util\Matcher\Matcher
|
||||
*/
|
||||
class ObjectMatcher extends Matcher
|
||||
{
|
||||
|
||||
/**
|
||||
* @var Parser
|
||||
*/
|
||||
private $parser;
|
||||
|
||||
/**
|
||||
* @param Parser $parser A Parser instance.
|
||||
*/
|
||||
public function __construct(Parser $parser)
|
||||
{
|
||||
$this->parser = $parser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if matcher can match the pattern
|
||||
*
|
||||
* @param mixed $pattern Pattern.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function canMatch($pattern)
|
||||
{
|
||||
if (! is_string($pattern)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->parser->hasValidSyntax($pattern)
|
||||
&& $this->parser->parse($pattern)->is('object');
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches value against the pattern
|
||||
*
|
||||
* @param mixed $value Checked value.
|
||||
* @param mixed $pattern Pattern.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function match($value, $pattern)
|
||||
{
|
||||
if (parent::match($value, $pattern)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Add ability to match serialized json.
|
||||
$lint = new JsonParser();
|
||||
if (is_string($value) && ($lint->lint($value) === null)) {
|
||||
$value = json_decode($value, true);
|
||||
}
|
||||
|
||||
if (! is_array($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that given value is assoc array.
|
||||
if (array_keys($value) === range(0, count($value) - 1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$typePattern = $this->parser->parse($pattern);
|
||||
|
||||
// Match all expanders.
|
||||
if (!$typePattern->matchExpanders($value)) {
|
||||
$this->error = $typePattern->getError();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Util\Matcher\Matcher;
|
||||
|
||||
use Coduo\PHPMatcher\Matcher\Matcher;
|
||||
use Coduo\PHPMatcher\Parser;
|
||||
|
||||
/**
|
||||
* Class WildcardMatcher
|
||||
* Replace default coduo/php-matcher WildcardMatcher in order to process
|
||||
* expanders.
|
||||
*
|
||||
* @package Common\Util\Matcher\Matcher
|
||||
*/
|
||||
class WildcardMatcher extends Matcher
|
||||
{
|
||||
|
||||
/**
|
||||
* @var Parser
|
||||
*/
|
||||
private $parser;
|
||||
|
||||
/**
|
||||
* @param Parser $parser A Parser instance.
|
||||
*/
|
||||
public function __construct(Parser $parser)
|
||||
{
|
||||
$this->parser = $parser;
|
||||
}
|
||||
|
||||
const MATCH_PATTERN = "@wildcard@";
|
||||
|
||||
/**
|
||||
* Checks if matcher can match the pattern
|
||||
*
|
||||
* @param mixed $pattern Pattern.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function canMatch($pattern)
|
||||
{
|
||||
return is_string($pattern)
|
||||
&& strpos($pattern, self::MATCH_PATTERN) !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches value against the pattern
|
||||
*
|
||||
* @param mixed $value Checked value.
|
||||
* @param mixed $pattern Pattern.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function match($value, $pattern)
|
||||
{
|
||||
$typePattern = $this->parser->parse($pattern);
|
||||
|
||||
// Match all expanders.
|
||||
if (!$typePattern->matchExpanders($value)) {
|
||||
$this->error = $typePattern->getError();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Util\Matcher;
|
||||
|
||||
use Coduo\PHPMatcher\Factory\SimpleFactory;
|
||||
use Coduo\PHPMatcher\Lexer;
|
||||
use Coduo\PHPMatcher\Matcher\ChainMatcher;
|
||||
use Coduo\PHPMatcher\Parser;
|
||||
use Coduo\PHPMatcher\Matcher;
|
||||
use Common\Util\Matcher\Expander as AppExpanders;
|
||||
use Common\Util\Matcher\Matcher as AppMatchers;
|
||||
|
||||
/**
|
||||
* Class MatcherFactory
|
||||
* @package Common\Util\Matcher
|
||||
*/
|
||||
class MatcherFactory extends SimpleFactory
|
||||
{
|
||||
|
||||
/**
|
||||
* @var Parser
|
||||
*/
|
||||
private static $parser;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private static $additionalExpanders = [
|
||||
'field' => AppExpanders\FieldExpander::class,
|
||||
'some' => AppExpanders\SomeExpander::class,
|
||||
'every' => AppExpanders\EveryExpander::class,
|
||||
'length' => AppExpanders\LengthExpander::class,
|
||||
'one' => AppExpanders\OneExpander::class,
|
||||
'type' => AppExpanders\TypeExpander::class,
|
||||
'entity' => AppExpanders\EntityExpander::class,
|
||||
'not' => AppExpanders\NotExpander::class,
|
||||
'gte' => AppExpanders\GteExpander::class,
|
||||
'between' => AppExpanders\BetweenExpander::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* @return ChainMatcher
|
||||
*/
|
||||
protected function buildScalarMatchers()
|
||||
{
|
||||
$parser = $this->buildParser();
|
||||
|
||||
return new Matcher\ChainMatcher([
|
||||
// Default matchers.
|
||||
new Matcher\CallbackMatcher(),
|
||||
new Matcher\ExpressionMatcher(),
|
||||
new Matcher\NullMatcher(),
|
||||
new Matcher\StringMatcher($parser),
|
||||
new Matcher\IntegerMatcher($parser),
|
||||
new Matcher\BooleanMatcher(),
|
||||
new Matcher\DoubleMatcher($parser),
|
||||
new Matcher\NumberMatcher(),
|
||||
new Matcher\ScalarMatcher(),
|
||||
|
||||
// Custom matchers.
|
||||
new AppMatchers\ObjectMatcher($parser),
|
||||
new AppMatchers\WildcardMatcher($parser),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Parser
|
||||
*/
|
||||
protected function buildParser()
|
||||
{
|
||||
if (!self::$parser) {
|
||||
// Register all expanders.
|
||||
$expanderInitializer = new Parser\ExpanderInitializer();
|
||||
|
||||
foreach (self::$additionalExpanders as $name => $class) {
|
||||
$expanderInitializer->setExpanderDefinition($name, $class);
|
||||
}
|
||||
|
||||
self::$parser = new Parser(new Lexer(), $expanderInitializer);
|
||||
}
|
||||
|
||||
return self::$parser;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Coduo\PHPMatcher\Matcher\ChainMatcher
|
||||
*/
|
||||
protected function buildMatchers()
|
||||
{
|
||||
$scalarMatchers = $this->buildScalarMatchers();
|
||||
$orMatcher = $this->buildOrMatcher();
|
||||
|
||||
$chainMatcher = new Matcher\ChainMatcher([
|
||||
$scalarMatchers,
|
||||
$orMatcher,
|
||||
new AppMatchers\JsonMatcher($orMatcher),
|
||||
new Matcher\XmlMatcher($orMatcher),
|
||||
new Matcher\TextMatcher($scalarMatchers, $this->buildParser()),
|
||||
]);
|
||||
|
||||
return $chainMatcher;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Util\Metadata;
|
||||
|
||||
use ApiBundle\Serializer\Metadata\Metadata;
|
||||
use ApiBundle\Serializer\Metadata\PropertyMetadata;
|
||||
use AppBundle\Entity\EntityInterface;
|
||||
|
||||
/**
|
||||
* Class EntityMetadata
|
||||
* Contains all entity metadata for matcher.
|
||||
*
|
||||
* @package Common\Util\Metadata
|
||||
*/
|
||||
class EntityMetadata
|
||||
{
|
||||
|
||||
/**
|
||||
* @var Metadata
|
||||
*/
|
||||
private $metadata;
|
||||
|
||||
/**
|
||||
* @var array[]
|
||||
*/
|
||||
private $cache = [];
|
||||
|
||||
/**
|
||||
* @param Metadata $metadata A Metadata instance.
|
||||
*/
|
||||
public function __construct(Metadata $metadata)
|
||||
{
|
||||
$this->metadata = $metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get pattern for specified groups.
|
||||
*
|
||||
* @param array $groups Serialization groups.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getPattern(array $groups)
|
||||
{
|
||||
sort($groups);
|
||||
$key = serialize($groups);
|
||||
if (! isset($this->cache[$key])) {
|
||||
$properties = $this->metadata->getProperties($groups);
|
||||
|
||||
$this->cache[$key] = [];
|
||||
|
||||
if ($this->metadata->implementsInterface(EntityInterface::class)) {
|
||||
$this->cache[$key]['type'] = '@string@';
|
||||
}
|
||||
|
||||
foreach ($properties as $property) {
|
||||
$this->cache[$key][$property->getName()] =
|
||||
$this->generateProperty($property, $groups);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->cache[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from entity metadata type to PHPMatcher type.
|
||||
*
|
||||
* @param PropertyMetadata $property A PropertyMetadata instance.
|
||||
* @param array $groups A serialization groups.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function generateProperty(PropertyMetadata $property, array $groups)
|
||||
{
|
||||
$type = $property->getType();
|
||||
|
||||
switch ($type) {
|
||||
// Associated object - another entity.
|
||||
// Use entity expander with same serialization groups.
|
||||
case PropertyMetadata::TYPE_ENTITY:
|
||||
$type = \app\c\entityFqcnToShort($property->getActualType());
|
||||
$expander = "entity('{$type}', '". implode(',', $groups) ."')";
|
||||
$type = "@object@.{$expander}";
|
||||
if ($property->isNullable()) {
|
||||
$type = "@wildcard@.oneOf(isEmpty(), {$expander})";
|
||||
}
|
||||
break;
|
||||
|
||||
// Enum type.
|
||||
case PropertyMetadata::TYPE_ENUM:
|
||||
$type = '@string@';
|
||||
break;
|
||||
|
||||
// Collection of associated entities.
|
||||
case PropertyMetadata::TYPE_COLLECTION:
|
||||
$type = \app\c\entityFqcnToShort($property->getActualType());
|
||||
$reflection = new \ReflectionClass($property->getActualType());
|
||||
if ($reflection->isAbstract()) {
|
||||
$type = '@array@';
|
||||
} else {
|
||||
$expander = "entity('{$type}', '" . implode(',', $groups) . "')";
|
||||
$type = "@array@.every({$expander})";
|
||||
if ($property->isNullable()) {
|
||||
$type = "@wildcard@.oneOf(isEmpty(), every({$expander}))";
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
// For double type also use integer type checker.
|
||||
case PropertyMetadata::TYPE_DOUBLE:
|
||||
$type = "@wildcard@.oneOf(type('integer'), type('double'))";
|
||||
if ($property->isNullable()) {
|
||||
$type = "@wildcard@.oneOf(isEmpty(), type('integer'), type('double'))";
|
||||
}
|
||||
break;
|
||||
|
||||
// For DateTime instances use 'string' matcher with 'isDateTime'
|
||||
// expander.
|
||||
case PropertyMetadata::TYPE_DATE:
|
||||
$type = '@string@.isDateTime()';
|
||||
if ($property->isNullable()) {
|
||||
$type = '@wildcard@.oneOf(isEmpty(), isDateTime())';
|
||||
}
|
||||
break;
|
||||
|
||||
// Process inline objects.
|
||||
case PropertyMetadata::TYPE_GROUP:
|
||||
$type = '@object@';
|
||||
if ($property->isNullable()) {
|
||||
$type = '@wildcard@';
|
||||
}
|
||||
break;
|
||||
|
||||
// Other types like string, integer and etc.
|
||||
default:
|
||||
if ($property->isNullable()) {
|
||||
$type = "@wildcard@.oneOf(isEmpty(), type('$type'))";
|
||||
} else {
|
||||
$type = '@'. $type .'@';
|
||||
}
|
||||
}
|
||||
|
||||
return $type;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Util\Processor;
|
||||
|
||||
use Common\Util\Processor\ExpressionLanguage\TestExpressionLanguage;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
|
||||
|
||||
/**
|
||||
* Class DataProcessor
|
||||
* Process data before sending to server.
|
||||
*
|
||||
* @package Common\Util\Processor
|
||||
*/
|
||||
class DataProcessor
|
||||
{
|
||||
|
||||
/**
|
||||
* @var ExpressionLanguage
|
||||
*/
|
||||
private $language;
|
||||
|
||||
/**
|
||||
* DataProcessor constructor.
|
||||
*
|
||||
* @param ContainerInterface $container A ContainerInterface instance.
|
||||
*/
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
$this->language = new TestExpressionLanguage($container);
|
||||
$this->registerFunctions();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $data Process data send to server and replace patterns.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function process($data)
|
||||
{
|
||||
if (is_array($data)) {
|
||||
// Recursively process arrays.
|
||||
return array_map(function ($data) {
|
||||
return $this->process($data);
|
||||
}, $data);
|
||||
} elseif (is_string($data)) {
|
||||
// Process string data.
|
||||
$replacer = function ($param) {
|
||||
// Sanitize params.
|
||||
$param = str_replace('\\"', '\'', trim(current($param), '#'));
|
||||
|
||||
return $this->language->evaluate($param);
|
||||
};
|
||||
|
||||
return preg_replace_callback("/#.+?#/", $replacer, $data);
|
||||
}
|
||||
|
||||
// Not change other variable types.
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $arguments Arguments specified by expression language.
|
||||
* @param mixed $time Pass to DateTime constructor.
|
||||
*
|
||||
* @return \DateTime
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function createDate($arguments, $time = 'now')
|
||||
{
|
||||
return new \DateTime($time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register custom expression language functions.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function registerFunctions()
|
||||
{
|
||||
$dummy = function () {
|
||||
// do nothing.
|
||||
};
|
||||
|
||||
$this->language->register('date', $dummy, [ $this, 'createDate' ]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Util\Processor\ExpressionLanguage\Provider;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Mapping\ClassMetadataInfo;
|
||||
use Symfony\Component\ExpressionLanguage\ExpressionFunction;
|
||||
use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;
|
||||
|
||||
/**
|
||||
* Class EntityGetterProvider
|
||||
* Register function like 'getUser' or 'getStoredQuery' for fetching single
|
||||
* entity from database.
|
||||
*
|
||||
* @package Common\Util\Processor\ExpressionLanguage\Provider
|
||||
*
|
||||
*/
|
||||
class EntityGetterProvider implements ExpressionFunctionProviderInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var EntityManagerInterface
|
||||
*/
|
||||
private $em;
|
||||
|
||||
/**
|
||||
* EntityGetterProvider constructor.
|
||||
*
|
||||
* @param EntityManagerInterface $em A EntityManagerInterface instance.
|
||||
*/
|
||||
public function __construct(EntityManagerInterface $em)
|
||||
{
|
||||
$this->em = $em;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ExpressionFunction[] An array of Function instances.
|
||||
*/
|
||||
public function getFunctions()
|
||||
{
|
||||
/**
|
||||
* Dummy compiler.
|
||||
* We use this expression function only in runtime and not compile its.
|
||||
*/
|
||||
$compiler = function () {
|
||||
};
|
||||
|
||||
$functions = [];
|
||||
/** @var ClassMetadataInfo[] $metadataList */
|
||||
$metadataList = $this->em->getMetadataFactory()->getAllMetadata();
|
||||
foreach ($metadataList as $metadata) {
|
||||
$name = $metadata->getName();
|
||||
$shortName = substr($name, strrpos($name, '\\') + 1);
|
||||
$fnName = 'get'. $shortName;
|
||||
|
||||
if (strpos($name, 'Entity\\'. $shortName) === false) {
|
||||
// Process only entities inside 'Entity' directory.
|
||||
continue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $arguments Arguments specified by expression language
|
||||
* @param array $criteria Search criteria.
|
||||
*
|
||||
* @return null|object Found entity or null.
|
||||
*/
|
||||
$evaluator = function ($arguments, array $criteria) use ($name) {
|
||||
return $this->em->getRepository($name)->findOneBy($criteria);
|
||||
};
|
||||
$functions[] = new ExpressionFunction($fnName, $compiler, $evaluator);
|
||||
}
|
||||
|
||||
return $functions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Util\Processor\ExpressionLanguage;
|
||||
|
||||
use Common\Util\Processor\ExpressionLanguage\Provider\EntityGetterProvider;
|
||||
use Doctrine\Bundle\DoctrineBundle\Registry;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\ExpressionLanguage\ExpressionFunction;
|
||||
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
|
||||
|
||||
/**
|
||||
* Class TestExpressionLanguage
|
||||
* @package Common\Util\Processor\ExpressionLanguage
|
||||
*/
|
||||
class TestExpressionLanguage extends ExpressionLanguage
|
||||
{
|
||||
|
||||
/**
|
||||
* TestExpressionLanguage constructor.
|
||||
*
|
||||
* @param ContainerInterface $container A ContainerInterface instance.
|
||||
*/
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
/** @var Registry $doctrine */
|
||||
$doctrine = $container->get('doctrine');
|
||||
|
||||
$dummy = function () {
|
||||
// Dummy function for compiler argument of ExpressionFunction.
|
||||
};
|
||||
|
||||
parent::__construct(null, [
|
||||
new EntityGetterProvider($doctrine->getManager()),
|
||||
]);
|
||||
|
||||
// Add 'now' function which return current date.
|
||||
$this->addFunction(new ExpressionFunction('now', $dummy, function () {
|
||||
return date_create();
|
||||
}));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user