first commit
This commit is contained in:
302
tests/functional/QueryTest/FilterTest.json
Normal file
302
tests/functional/QueryTest/FilterTest.json
Normal file
@@ -0,0 +1,302 @@
|
||||
{
|
||||
"sections": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Has children",
|
||||
"lead_text": "text"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Child",
|
||||
"lead_text": "text"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "No children",
|
||||
"lead_text": "text"
|
||||
}
|
||||
],
|
||||
"sections_relation": [
|
||||
{
|
||||
"id_section": 1,
|
||||
"id_topsection": null,
|
||||
"position": 1
|
||||
},
|
||||
{
|
||||
"id_section": 2,
|
||||
"id_topsection": 1,
|
||||
"position": 2
|
||||
},
|
||||
{
|
||||
"id_section": 3,
|
||||
"id_topsection": null,
|
||||
"position": 3
|
||||
}
|
||||
],
|
||||
"producers": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Producer 1",
|
||||
"photo": "x.jpg",
|
||||
"web": "http://www.example.com"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Producer 2",
|
||||
"photo": "x.jpg",
|
||||
"web": "http://www.example.com"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"name": "Producer 5",
|
||||
"photo": "x.jpg",
|
||||
"web": "http://www.example.com"
|
||||
}
|
||||
],
|
||||
"products": [
|
||||
{
|
||||
"id": 1,
|
||||
"short_descr": "short",
|
||||
"long_descr": "long",
|
||||
"parameters": "params",
|
||||
"producer": 1,
|
||||
"vat": 1,
|
||||
"campaign": "N",
|
||||
"price": 0,
|
||||
"discount": 0,
|
||||
"figure": "Y",
|
||||
"price_for_discount": null
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"short_descr": "short",
|
||||
"long_descr": "long",
|
||||
"parameters": "params",
|
||||
"producer": 2,
|
||||
"vat": 1,
|
||||
"campaign": "N",
|
||||
"price": 10,
|
||||
"discount": 0,
|
||||
"figure": "Y"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"short_descr": "short",
|
||||
"long_descr": "long",
|
||||
"parameters": "params",
|
||||
"producer": 1,
|
||||
"vat": 1,
|
||||
"campaign": "N",
|
||||
"price": 20,
|
||||
"discount": 0,
|
||||
"figure": "Y"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"short_descr": "short",
|
||||
"long_descr": "long",
|
||||
"parameters": "params",
|
||||
"producer": 1,
|
||||
"vat": 1,
|
||||
"campaign": "N",
|
||||
"price": 30,
|
||||
"discount": 0,
|
||||
"figure": "Y"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"short_descr": "short",
|
||||
"long_descr": "long",
|
||||
"parameters": "params",
|
||||
"producer": 1,
|
||||
"vat": 1,
|
||||
"campaign": "N",
|
||||
"price": 40,
|
||||
"discount": 0,
|
||||
"figure": "Y"
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"short_descr": "short",
|
||||
"long_descr": "long",
|
||||
"parameters": "params",
|
||||
"producer": 5,
|
||||
"vat": 1,
|
||||
"campaign": "N",
|
||||
"price": 120,
|
||||
"discount": 10,
|
||||
"figure": "Y"
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"short_descr": "short",
|
||||
"long_descr": "long",
|
||||
"parameters": "params",
|
||||
"producer": null,
|
||||
"vat": 1,
|
||||
"campaign": "N",
|
||||
"price": 120,
|
||||
"discount": 20,
|
||||
"figure": "Y"
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"short_descr": "short",
|
||||
"long_descr": "long",
|
||||
"parameters": "params",
|
||||
"producer": null,
|
||||
"vat": 1,
|
||||
"campaign": "N",
|
||||
"price": 0,
|
||||
"discount": 0,
|
||||
"figure": "N"
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"short_descr": "short",
|
||||
"long_descr": "long",
|
||||
"parameters": "params",
|
||||
"producer": null,
|
||||
"vat": 1,
|
||||
"campaign": "N",
|
||||
"price": 120,
|
||||
"discount": 20,
|
||||
"figure": "Y",
|
||||
"price_for_discount": 200
|
||||
}
|
||||
],
|
||||
"products_in_sections": [
|
||||
{
|
||||
"id_section": 1,
|
||||
"id_product": 1
|
||||
},
|
||||
{
|
||||
"id_section": 2,
|
||||
"id_product": 2
|
||||
},
|
||||
{
|
||||
"id_section": 2,
|
||||
"id_product": 3
|
||||
},
|
||||
{
|
||||
"id_section": 2,
|
||||
"id_product": 4
|
||||
},
|
||||
{
|
||||
"id_section": 3,
|
||||
"id_product": 5
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Size",
|
||||
"value_type": "int"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Color",
|
||||
"value_type": "list"
|
||||
}
|
||||
],
|
||||
"parameters_list": [
|
||||
{
|
||||
"id": 1,
|
||||
"id_parameter": 2,
|
||||
"value": "black"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"id_parameter": 2,
|
||||
"value": "white"
|
||||
}
|
||||
],
|
||||
"parameters_products": [
|
||||
{
|
||||
"id": 1,
|
||||
"id_product": 3,
|
||||
"id_parameter": 1,
|
||||
"value_list": null,
|
||||
"value_char": null,
|
||||
"value_float": 5
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"id_product": 3,
|
||||
"id_parameter": 2,
|
||||
"value_list": 1,
|
||||
"value_char": null,
|
||||
"value_float": null
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"id_product": 4,
|
||||
"id_parameter": 2,
|
||||
"value_list": 1,
|
||||
"value_char": null,
|
||||
"value_float": null
|
||||
}
|
||||
],
|
||||
"products_variations": [
|
||||
{
|
||||
"id": 1,
|
||||
"id_product": 4,
|
||||
"title": "title 1",
|
||||
"price": null,
|
||||
"figure": "Y"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"id_product": 4,
|
||||
"title": "title 2",
|
||||
"price": 1,
|
||||
"figure": "N"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"id_product": 5,
|
||||
"title": "title 2",
|
||||
"price": 50,
|
||||
"figure": "Y"
|
||||
}
|
||||
],
|
||||
"pricelists": [
|
||||
{
|
||||
"id": 1,
|
||||
"currency": "CZK",
|
||||
"name": "Test 1"
|
||||
}
|
||||
],
|
||||
"pricelists_products": [
|
||||
{
|
||||
"id": 1,
|
||||
"id_pricelist": 1,
|
||||
"id_product": 1,
|
||||
"id_variation": null,
|
||||
"price": 44.3782,
|
||||
"price_for_discount": null,
|
||||
"discount": 25,
|
||||
"integrity_unique": "1-1"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"id_pricelist": 1,
|
||||
"id_product": 2,
|
||||
"id_variation": null,
|
||||
"price": 41.3223,
|
||||
"price_for_discount": 49.5868,
|
||||
"discount": null,
|
||||
"integrity_unique": "1-2"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"id_pricelist": 1,
|
||||
"id_product": 3,
|
||||
"id_variation": null,
|
||||
"price": 41.3223,
|
||||
"price_for_discount": 61.9835,
|
||||
"discount": 10,
|
||||
"integrity_unique": "1-2"
|
||||
}
|
||||
]
|
||||
}
|
||||
234
tests/functional/QueryTest/FilterTest.php
Normal file
234
tests/functional/QueryTest/FilterTest.php
Normal file
@@ -0,0 +1,234 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Created by PhpStorm.
|
||||
* User: filip
|
||||
* Date: 5/17/16
|
||||
* Time: 9:31 AM.
|
||||
*/
|
||||
|
||||
namespace QueryTest;
|
||||
|
||||
use KupShop\CatalogBundle\ProductList\FilterParams;
|
||||
use KupShop\KupShopBundle\Context\UserContext;
|
||||
use KupShop\KupShopBundle\PriceType\PriceForDiscountPriceType;
|
||||
use KupShop\KupShopBundle\PriceType\PriceIfOnlyDiscountPriceType;
|
||||
use KupShop\KupShopBundle\PriceType\PriceTypeInterface;
|
||||
use KupShop\KupShopBundle\PriceType\PriceWithoutDiscountPriceType;
|
||||
use KupShop\KupShopBundle\Util\Contexts;
|
||||
use KupShop\PricelistBundle\Context\PricelistContext;
|
||||
use Query\Filter;
|
||||
use Query\Operator;
|
||||
use Query\Product;
|
||||
|
||||
class FilterTest extends \DatabaseTestCase
|
||||
{
|
||||
/** @dataProvider data_testVisibilityFilter */
|
||||
public function testVisibilityFilter(int $expectedCount, callable $figureSpec, string $type = FilterParams::ENTITY_PRODUCT): void
|
||||
{
|
||||
$qb = sqlQueryBuilder()
|
||||
->select('p.id')
|
||||
->joinVariationsOnProducts()
|
||||
->from('products', 'p')
|
||||
->where($figureSpec);
|
||||
|
||||
if ($type === FilterParams::ENTITY_VARIATION) {
|
||||
$qb->groupBy('p.id, pv.id');
|
||||
} else {
|
||||
$qb->groupBy('p.id');
|
||||
}
|
||||
|
||||
$count = $qb->execute()->rowCount();
|
||||
|
||||
$this->assertEquals($expectedCount, $count);
|
||||
}
|
||||
|
||||
public function data_testVisibilityFilter(): iterable
|
||||
{
|
||||
yield 'Test load visible products' => [8, Filter::isVisible()];
|
||||
yield 'Test load hidden products' => [2, Operator::not(Filter::isVisible())];
|
||||
yield 'Test load sold out products' => [0, Filter::byFigure('O')];
|
||||
yield 'Test load visible and hidden products' => [9, Operator::orX(Filter::isVisible(), Operator::not(Filter::isVisible()))];
|
||||
|
||||
// Variations list - 1 visible variation + 6 visible products = 7
|
||||
yield 'Test load visible variation product list' => [8, Filter::isVisible(), FilterParams::ENTITY_VARIATION];
|
||||
// Variation list - 1 hidden variation and 1 hidden product
|
||||
yield 'Test load hidden variation product list' => [2, Operator::not(Filter::isVisible()), FilterParams::ENTITY_VARIATION];
|
||||
// Variation list - 6 products + 3 variation products
|
||||
yield 'Test load visible and hidden variation product list' => [10, Operator::orX(Filter::isVisible(), Operator::not(Filter::isVisible())), FilterParams::ENTITY_VARIATION];
|
||||
}
|
||||
|
||||
/** @dataProvider dataSet_testByDiscountRange */
|
||||
public function testByDiscountRange(\Range $range, ?int $priceListId, ?PriceTypeInterface $originalPriceType, array $expectedIDs): void
|
||||
{
|
||||
if ($priceListId) {
|
||||
Contexts::get(PricelistContext::class)->activate($priceListId);
|
||||
}
|
||||
|
||||
if ($originalPriceType) {
|
||||
Contexts::clear();
|
||||
$this->withMockedUserContext($originalPriceType);
|
||||
}
|
||||
|
||||
$IDs = sqlQueryBuilder()
|
||||
->select('p.id')
|
||||
->from('products', 'p')
|
||||
->where(Filter::byDiscountRange($range))
|
||||
->groupBy('p.id')
|
||||
->execute()
|
||||
->fetchFirstColumn();
|
||||
|
||||
$this->assertEquals($expectedIDs, $IDs);
|
||||
}
|
||||
|
||||
public function dataSet_testByDiscountRange(): iterable
|
||||
{
|
||||
yield 'Filter discount range 1-5' => [new \Range(1, 5), null, null, []];
|
||||
yield 'Filter discount range 5-12' => [new \Range(5, 12), null, null, [6]];
|
||||
yield 'Filter discount range 10-15' => [new \Range(10, 15), null, null, [6]];
|
||||
yield 'Filter discount range 10-20' => [new \Range(10, 20), null, null, [6, 7]];
|
||||
yield 'Filter discount range 20-35' => [new \Range(20, 35), null, null, [7]];
|
||||
|
||||
yield 'Filter with price list range 1-10' => [new \Range(1, 10), 1, null, []];
|
||||
yield 'Filter with price list range 20-30' => [new \Range(20, 30), 1, null, [1]];
|
||||
yield 'Filter with price list range 15-20 - discount calculated using price_for_discount' => [new \Range(15, 20), 1, null, [2]];
|
||||
yield 'Filter with price list range 15-20 - with explicit discount field' => [new \Range(15, 20), 1, new PriceIfOnlyDiscountPriceType(new PriceForDiscountPriceType()), []];
|
||||
yield 'Filter with price list range 30-40 - with explicit discount field' => [new \Range(30, 40), 1, new PriceIfOnlyDiscountPriceType(new PriceForDiscountPriceType()), [3]];
|
||||
|
||||
yield 'Filter not discounted products' => [new \Range(null, 0), null, null, [1, 2, 3, 4, 5, 8]];
|
||||
yield 'Filter not discounted products for price list' => [new \Range(null, 0), 1, null, [4, 5, 6, 7, 8]];
|
||||
yield 'Filter not discounted products for price list - without price history' => [new \Range(null, 0), 1, new PriceWithoutDiscountPriceType(), [2, 4, 5, 6, 7, 8, 9]];
|
||||
yield 'Filter not discounted products for price list - with explicit discount field' => [new \Range(null, 0), 1, new PriceIfOnlyDiscountPriceType(new PriceForDiscountPriceType()), [2, 4, 5, 6, 7, 8, 9]];
|
||||
}
|
||||
|
||||
/** @dataProvider dataSet_testDiscountFieldDefinition */
|
||||
public function testDiscountFieldDefinition(int $productId, ?int $priceListId, ?PriceTypeInterface $originalPriceType, float $expectedDiscount): void
|
||||
{
|
||||
if ($priceListId) {
|
||||
Contexts::get(PricelistContext::class)->activate($priceListId);
|
||||
}
|
||||
|
||||
if ($originalPriceType) {
|
||||
$this->withMockedUserContext($originalPriceType);
|
||||
}
|
||||
|
||||
$discountFieldDefinition = $this->get(UserContext::class)->getOriginalPriceType()->getDiscountFieldDefinition();
|
||||
|
||||
$discount = sqlQueryBuilder()->select($discountFieldDefinition->getField())
|
||||
->fromProducts()
|
||||
->joinVariationsOnProducts()
|
||||
->where(Operator::equals(['p.id' => $productId]))
|
||||
->andWhere($discountFieldDefinition->getSpec())
|
||||
->execute()
|
||||
->fetchOne();
|
||||
|
||||
$this->assertEqualsWithDelta($expectedDiscount, $discount, 0.1);
|
||||
}
|
||||
|
||||
public function dataSet_testDiscountFieldDefinition(): iterable
|
||||
{
|
||||
yield 'Product with zero price' => [1, null, null, 0];
|
||||
yield 'Product with zero discount' => [2, null, null, 0];
|
||||
yield 'Product with 10% discount' => [6, null, null, 10];
|
||||
yield 'Product with price for discount' => [9, null, null, 100 - (((120 * 0.8) * 100) / 200)];
|
||||
yield 'Product with discount from pricelist' => [1, 1, null, 25];
|
||||
yield 'Product with price for discount from pricelist' => [2, 1, null, 100 - ((41.3223 * 100) / 49.5868)];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataSet_testByProducers
|
||||
*/
|
||||
public function testByProducers(array $producerIds, int $expectedCount, bool $negative): void
|
||||
{
|
||||
$this->assertSame($expectedCount, sqlQueryBuilder()
|
||||
->select('p.id')
|
||||
->from('products', 'p')
|
||||
->andWhere($negative
|
||||
? Operator::not(Filter::byProducers($producerIds))
|
||||
: Filter::byProducers($producerIds)
|
||||
)
|
||||
->execute()
|
||||
->rowCount()
|
||||
);
|
||||
}
|
||||
|
||||
public function dataSet_testByProducers(): array
|
||||
{
|
||||
return [
|
||||
[[1], 4, false],
|
||||
[[2], 1, false],
|
||||
[[3], 0, false],
|
||||
[[1, 2, 3], 5, false],
|
||||
[[1337], 9, true],
|
||||
[[1337], 0, false],
|
||||
[[1, 2, 3], 4, true],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataSet_testByPriceRange
|
||||
*/
|
||||
public function testByPriceRange(\Range $range, $expectedCount)
|
||||
{
|
||||
$count = sqlQueryBuilder()
|
||||
->select('p.id')
|
||||
->from('products', 'p')
|
||||
->where(Filter::byPriceRange($range))
|
||||
->execute()
|
||||
->rowCount();
|
||||
|
||||
$this->assertSame($expectedCount, $count);
|
||||
}
|
||||
|
||||
public function dataSet_testByPriceRange()
|
||||
{
|
||||
return [
|
||||
[new \Range(5 * 1.21, 5 * 1.21), 0],
|
||||
[new \Range(10 * 1.21, 10 * 1.21), 1],
|
||||
[new \Range(10 * 1.21, 30 * 1.21), 3],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataSet_testByProductPriceRange
|
||||
*/
|
||||
public function testByProductPriceRange(\Range $range, $expectedCount)
|
||||
{
|
||||
$qb = sqlQueryBuilder()
|
||||
->select('p.id')
|
||||
->from('products', 'p')
|
||||
->where(Filter::byProductPriceRange($range))
|
||||
->groupBy('p.id')
|
||||
->execute();
|
||||
|
||||
$count = $qb->rowCount();
|
||||
$this->assertSame($expectedCount, $count);
|
||||
}
|
||||
|
||||
public function dataSet_testByProductPriceRange()
|
||||
{
|
||||
return [
|
||||
[new \Range(5 * 1.21, 5 * 1.21), 0],
|
||||
[new \Range(30 * 1.21, 30 * 1.21), 1],
|
||||
[new \Range(30 * 1.21, 40 * 1.21), 2],
|
||||
];
|
||||
}
|
||||
|
||||
public function getDataSet()
|
||||
{
|
||||
return $this->getJsonDataSetFromFile();
|
||||
}
|
||||
|
||||
private function withMockedUserContext(PriceTypeInterface $originalPriceType): void
|
||||
{
|
||||
$mockedUserContext = $this->getMockBuilder(UserContext::class)
|
||||
->onlyMethods(['loadOriginalPriceType'])
|
||||
->getMock();
|
||||
|
||||
$mockedUserContext->expects(self::once())
|
||||
->method('loadOriginalPriceType')
|
||||
->willReturn($originalPriceType);
|
||||
|
||||
$this->set(UserContext::class, $mockedUserContext);
|
||||
}
|
||||
}
|
||||
0
tests/functional/QueryTest/FilterTest.yml
Normal file
0
tests/functional/QueryTest/FilterTest.yml
Normal file
92
tests/functional/QueryTest/OperatorTest.php
Normal file
92
tests/functional/QueryTest/OperatorTest.php
Normal file
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace QueryTest;
|
||||
|
||||
use Doctrine\DBAL\Query\QueryBuilder;
|
||||
use Query\Operator;
|
||||
use Query\Operator as Op;
|
||||
|
||||
class OperatorTest extends \DatabaseTestCase
|
||||
{
|
||||
public function testAndX()
|
||||
{
|
||||
$noName = function (QueryBuilder $qb) {
|
||||
return $this->expr()->isNull('alias.name');
|
||||
};
|
||||
|
||||
$hasPrice = function (QueryBuilder $qb) {
|
||||
return $this->expr()->isNotNull('alias.price');
|
||||
};
|
||||
|
||||
$andExpr = Op::andX($noName, $hasPrice);
|
||||
$where = $andExpr(sqlQueryBuilder());
|
||||
|
||||
$this->assertSame('(alias.name IS NULL) AND (alias.price IS NOT NULL)', (string) $where);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataSet_testBetween
|
||||
*/
|
||||
public function testBetween($min, $max, $expected)
|
||||
{
|
||||
$between = Op::between('field', new \Range($min, $max));
|
||||
$where = sqlQueryBuilder()->evaluateClosures([$between])[0];
|
||||
|
||||
$this->assertRegExp("@{$expected}@", (string) $where);
|
||||
}
|
||||
|
||||
public function dataSet_testBetween()
|
||||
{
|
||||
return [
|
||||
[1, 2, '\(field >= :between_min_\d+\) AND \(field <= :between_max_\d+\)'],
|
||||
[1, null, 'field >= :between_min_\d+'],
|
||||
[null, 2, 'field <= :between_max_\d+'],
|
||||
[null, null, ''],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \InvalidArgumentException
|
||||
*/
|
||||
public function testFindInSetThrows()
|
||||
{
|
||||
Operator::findInSet([1, 2, 3], 'column', 'x');
|
||||
}
|
||||
|
||||
public function testFindInSetAnd()
|
||||
{
|
||||
$expr = Operator::findInSet([1, 2], 'column', 'AND');
|
||||
|
||||
$this->assertRegExp(
|
||||
'@\(FIND_IN_SET\(:needle_\d+, `column`\)\) AND \(FIND_IN_SET\(:needle_\d+, `column`\)\)@',
|
||||
(string) sqlQueryBuilder()->evaluateClosures([$expr])[0]
|
||||
);
|
||||
}
|
||||
|
||||
public function testFindInSetOr()
|
||||
{
|
||||
$expr = Operator::findInSet([1, 2], 'column', 'OR');
|
||||
|
||||
$this->assertRegExp(
|
||||
'@\(FIND_IN_SET\(:needle_\d+, `column`\)\) OR \(FIND_IN_SET\(:needle_\d+, `column`\)\)@',
|
||||
(string) sqlQueryBuilder()->evaluateClosures([$expr])[0]
|
||||
);
|
||||
}
|
||||
|
||||
public function testNot()
|
||||
{
|
||||
$isNull = function (QueryBuilder $qb) {
|
||||
return $this->expr()->isNull('alias.name');
|
||||
};
|
||||
$expr = Operator::not($isNull);
|
||||
$this->assertSame(
|
||||
'NOT (alias.name IS NULL)',
|
||||
(string) sqlQueryBuilder()->evaluateClosures([$expr])[0]
|
||||
);
|
||||
}
|
||||
|
||||
private function expr()
|
||||
{
|
||||
return sqlQueryBuilder()->expr();
|
||||
}
|
||||
}
|
||||
114
tests/functional/QueryTest/ProductTest.php
Normal file
114
tests/functional/QueryTest/ProductTest.php
Normal file
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace QueryTest;
|
||||
|
||||
use Query\Filter;
|
||||
use Query\Product;
|
||||
use Query\QueryBuilder;
|
||||
|
||||
class ProductTest extends \DatabaseTestCase
|
||||
{
|
||||
/** @var QueryBuilder */
|
||||
private $qb;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->qb = sqlQueryBuilder();
|
||||
}
|
||||
|
||||
public function testIsVisible()
|
||||
{
|
||||
$this->removeModule(\Modules::TRANSLATIONS, null);
|
||||
|
||||
$isVisible = Product::isVisible();
|
||||
$expression = $isVisible($this->qb);
|
||||
|
||||
$this->assertRegExp('/p.figure = :visible\d+/', $expression);
|
||||
preg_match('/p.figure = :(visible\d+)/', $expression, $matches);
|
||||
$this->assertSame('Y', $this->qb->getParameter($matches[1]));
|
||||
}
|
||||
|
||||
public function testIsVisibleTranslations()
|
||||
{
|
||||
$this->switchLanguage();
|
||||
|
||||
$isVisible = Product::isVisible();
|
||||
$expression = $isVisible($this->qb);
|
||||
|
||||
$join = $this->qb->getQueryPart('join');
|
||||
|
||||
$this->assertEquals([
|
||||
'joinType' => 'left',
|
||||
'joinTable' => 'products_translations',
|
||||
'joinAlias' => 'p_sk',
|
||||
'joinCondition' => 'p_sk.id_product = p.id AND p_sk.id_language = :translation_language_sk',
|
||||
], $join['p'][0]);
|
||||
|
||||
$this->assertRegExp('/COALESCE\(p_sk.figure, p.figure\) = :visible\d+/', $expression);
|
||||
}
|
||||
|
||||
public function testInSection()
|
||||
{
|
||||
$result = $this->qb->select('*')->from('products', 'p')->where(Product::inSection(3))->execute()->fetchAll();
|
||||
|
||||
$this->assertCount(3, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $expectedCount
|
||||
*
|
||||
* @dataProvider productsInSections
|
||||
*/
|
||||
public function testInSections(array $sectionIds, $expectedCount)
|
||||
{
|
||||
$count = $this->qb->select('*')->from('products', 'p')->where(Product::inSections($sectionIds))->execute()->rowCount();
|
||||
$this->assertSame($expectedCount, $count);
|
||||
}
|
||||
|
||||
public function productsInSections()
|
||||
{
|
||||
return [
|
||||
[[3], 3],
|
||||
[[2, 3], 5],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataSet_testInStore
|
||||
*
|
||||
* @param bool $useVariations
|
||||
* @param int $expectedCount
|
||||
*/
|
||||
public function testInStore($useVariations, $expectedCount)
|
||||
{
|
||||
$count = $this->qb->select('*')->from('products', 'p')->where(Product::inStore($useVariations))->execute()->rowCount();
|
||||
|
||||
$this->assertSame($expectedCount, $count);
|
||||
}
|
||||
|
||||
public function dataSet_testInStore()
|
||||
{
|
||||
$dataSet = [];
|
||||
$dataSet['no variations'] = [false, 7];
|
||||
if (findModule('products_variations')) {
|
||||
$dataSet['variations'] = [true, 13];
|
||||
}
|
||||
|
||||
return $dataSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo add yml data to products_of_suppliers and refactor this test
|
||||
*
|
||||
* @dataProvider dataSet_testInStore
|
||||
*/
|
||||
public function testInStoreSupplier($useVariations, $expectedCount)
|
||||
{
|
||||
$count = $this->qb->select('*')->from('products', 'p')
|
||||
->andWhere(Filter::byInStore(\Filter::IN_STORE_SUPPLIER, $useVariations))
|
||||
->execute()->rowCount();
|
||||
$this->assertSame($expectedCount, $count);
|
||||
}
|
||||
}
|
||||
369
tests/functional/QueryTest/QueryBuilderTest.php
Normal file
369
tests/functional/QueryTest/QueryBuilderTest.php
Normal file
@@ -0,0 +1,369 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Created by PhpStorm.
|
||||
* User: filip
|
||||
* Date: 5/13/16
|
||||
* Time: 10:30 AM.
|
||||
*/
|
||||
|
||||
namespace QueryTest;
|
||||
|
||||
use Doctrine\DBAL\Query\QueryBuilder;
|
||||
use Query\Operator;
|
||||
|
||||
class QueryBuilderTest extends \DatabaseTestCase
|
||||
{
|
||||
public function testGlobalFactoryFunction()
|
||||
{
|
||||
$this->assertInstanceOf('Query\QueryBuilder', sqlQueryBuilder());
|
||||
$this->assertNotSame(sqlQueryBuilder(), sqlQueryBuilder());
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testGlobalFactoryFunction
|
||||
*/
|
||||
public function testWhereUsesAnd()
|
||||
{
|
||||
$specStub = function ($field, $value) {
|
||||
return function (QueryBuilder $qb) use ($field, $value) {
|
||||
return $qb->expr()->eq($field, $value);
|
||||
};
|
||||
};
|
||||
|
||||
$rowCount = sqlQueryBuilder()->select('*')->from('users')->where(
|
||||
$specStub('city', "'Vrchlabí'"),
|
||||
$specStub('figure', "'Y'")
|
||||
)->execute()->rowCount();
|
||||
|
||||
$this->assertSame(2, $rowCount);
|
||||
}
|
||||
|
||||
public function testOrderBySql()
|
||||
{
|
||||
$qb = sqlQueryBuilder();
|
||||
$qb->orderBySql('expression < 0 Asc');
|
||||
$this->assertSame(['expression < 0 Asc'], $qb->getQueryPart('orderBy'));
|
||||
}
|
||||
|
||||
public function testMultiDirectValuesSql()
|
||||
{
|
||||
$qb = sqlQueryBuilder()
|
||||
->insert('languages')
|
||||
->multiDirectValues(
|
||||
[
|
||||
'id' => 've',
|
||||
'name' => 'veverky',
|
||||
'locale' => 've_VE',
|
||||
'translate' => 0,
|
||||
]
|
||||
)
|
||||
->multiDirectValues(
|
||||
[
|
||||
'id' => 'li',
|
||||
'name' => 'lišky',
|
||||
'locale' => 'li_LI',
|
||||
'translate' => 0,
|
||||
])
|
||||
->multiDirectValues(
|
||||
[
|
||||
'id' => 'ko',
|
||||
'name' => 'kočky',
|
||||
'locale' => 'ko_KO',
|
||||
'translate' => 0,
|
||||
]);
|
||||
$this->assertSame('INSERT INTO languages (`id`,`name`,`locale`,`translate`) VALUES (:multi0_id, :multi0_name, :multi0_locale, :multi0_translate),(:multi1_id, :multi1_name, :multi1_locale, :multi1_translate),(:multi2_id, :multi2_name, :multi2_locale, :multi2_translate) ', $qb->getSQL());
|
||||
}
|
||||
|
||||
public function testMultiValuesSql()
|
||||
{
|
||||
$qb = sqlQueryBuilder()
|
||||
->insert('languages')
|
||||
->multiValues(
|
||||
[
|
||||
[
|
||||
'id' => 've',
|
||||
'name' => 'veverky',
|
||||
'locale' => 've_VE',
|
||||
'translate' => 0,
|
||||
],
|
||||
[
|
||||
'id' => 'li',
|
||||
'name' => 'lišky',
|
||||
'locale' => 'li_LI',
|
||||
'translate' => 0,
|
||||
],
|
||||
[
|
||||
'id' => 'ko',
|
||||
'name' => 'kočky',
|
||||
'locale' => 'ko_KO',
|
||||
'translate' => 0,
|
||||
],
|
||||
]
|
||||
);
|
||||
$this->assertSame('INSERT INTO languages (id,name,locale,translate) VALUES (ve, veverky, ve_VE, 0),(li, lišky, li_LI, 0),(ko, kočky, ko_KO, 0) ', $qb->getSQL());
|
||||
}
|
||||
|
||||
public function testDirectValues(): void
|
||||
{
|
||||
$params = [
|
||||
'foo' => 'fooFoo',
|
||||
'bar' => 10,
|
||||
'baz' => '1970-01-01 00:00:00',
|
||||
];
|
||||
$queryBuilder = sqlQueryBuilder()
|
||||
->insert('users')
|
||||
->directValues($params);
|
||||
|
||||
$this->assertSame('INSERT INTO users (`foo`,`bar`,`baz`) VALUES (:foo, :bar, :baz) ', $queryBuilder->getSQL());
|
||||
$this->assertSame($params, $queryBuilder->getParameters());
|
||||
$this->assertSame(
|
||||
<<<EOT
|
||||
INSERT INTO users (`foo`, `bar`, `baz`)
|
||||
VALUES
|
||||
(
|
||||
'fooFoo', 10, '1970-01-01 00:00:00'
|
||||
)
|
||||
EOT,
|
||||
$queryBuilder->getRunnableSQL()
|
||||
);
|
||||
}
|
||||
|
||||
public function testForUpdate(): void
|
||||
{
|
||||
$queryBuilder = sqlQueryBuilder()
|
||||
->select('count(*)', 'foo', 'baz')
|
||||
->where('a', 'b.c')
|
||||
->forUpdate();
|
||||
|
||||
$this->assertSame('SELECT count(*), foo, baz WHERE (a) AND (b.c) FOR UPDATE', $queryBuilder->getSQL());
|
||||
}
|
||||
|
||||
public function testCalcRows(): void
|
||||
{
|
||||
$queryBuilder = sqlQueryBuilder()
|
||||
->select('*', 'foo')
|
||||
->from('t')
|
||||
->where('id >= :id')
|
||||
->setParameter('id', 1)
|
||||
->addCalcRows();
|
||||
|
||||
$this->assertSame('SELECT SQL_CALC_FOUND_ROWS *, foo FROM t WHERE id >= :id', $queryBuilder->getSQL());
|
||||
$this->assertSame(['id' => 1], $queryBuilder->getParameters());
|
||||
}
|
||||
|
||||
public function testOnDuplicateKeyUpdate(): void
|
||||
{
|
||||
$queryBuilder = sqlQueryBuilder()
|
||||
->insert('foo')
|
||||
->values(['username' => 'jarin'])
|
||||
->onDuplicateKeyUpdate(['foo', 'bar']);
|
||||
|
||||
$this->assertSame('INSERT INTO foo (username) VALUES (jarin) ON DUPLICATE KEY UPDATE foo=VALUES(foo), bar=VALUES(bar) ', $queryBuilder->getSQL());
|
||||
}
|
||||
|
||||
public function testOnDuplicateKeyUpdateException(): void
|
||||
{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage('Call of "onDuplicateKeyUpdate" is allowed only with insert query');
|
||||
sqlQueryBuilder()
|
||||
->select('foo')
|
||||
->values(['username' => 'jarin'])
|
||||
->onDuplicateKeyUpdate(['foo', 'bar'])
|
||||
->getSQL();
|
||||
}
|
||||
|
||||
public function testSendMaster(): void
|
||||
{
|
||||
$queryBuilder = sqlQueryBuilder()
|
||||
->select('foo')
|
||||
->from('t')
|
||||
->sendToMaster();
|
||||
|
||||
$this->assertSame('SELECT foo FROM t -- maxscale route to master', $queryBuilder->getSQL());
|
||||
}
|
||||
|
||||
public function testAddQueryBuilderParameters(): void
|
||||
{
|
||||
$queryBuilderForParameters = sqlQueryBuilder()
|
||||
->setParameter('foo', 1)
|
||||
->setParameter('bar', 'baz');
|
||||
|
||||
$queryBuilder = sqlQueryBuilder()
|
||||
->select('*')
|
||||
->from('t')
|
||||
->where('foo != :foo')
|
||||
->orWhere('bar = :bar')
|
||||
->addQueryBuilderParameters($queryBuilderForParameters);
|
||||
|
||||
$this->assertSame('SELECT * FROM t WHERE (foo != :foo) OR (bar = :bar)', $queryBuilder->getSQL());
|
||||
$this->assertSame(
|
||||
<<<EOT
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
t
|
||||
WHERE
|
||||
(foo != 1)
|
||||
OR (bar = 'baz')
|
||||
EOT,
|
||||
$queryBuilder->getRunnableSQL()
|
||||
);
|
||||
|
||||
$this->assertSame($queryBuilderForParameters->getParameters(), $queryBuilder->getParameters());
|
||||
}
|
||||
|
||||
public function testSetForceIndexForJoin(): void
|
||||
{
|
||||
$queryBuilder = sqlQueryBuilder()
|
||||
->select('id')
|
||||
->from('t')
|
||||
->setForceIndexForJoin('a', 'a_table');
|
||||
|
||||
// ignore when using non-existing join alias
|
||||
$this->assertSame('SELECT id FROM t', $queryBuilder->getSQL());
|
||||
|
||||
$queryBuilder = sqlQueryBuilder()
|
||||
->select('id', 'MAX(max_1, max_2) as j')
|
||||
->from('t', 'a')
|
||||
->leftJoin('a', 'products', 'p', 'p.id = a.id_product')
|
||||
->setForceIndexForJoin('j', 'pricelist_product')
|
||||
->setForceIndexForJoin('a', 'blbost');
|
||||
|
||||
// ignore when using force index for from clause alias or select clause alias
|
||||
$this->assertSame('SELECT id, MAX(max_1, max_2) as j FROM t a LEFT JOIN products p ON p.id = a.id_product', $queryBuilder->getSQL());
|
||||
|
||||
$queryBuilder = sqlQueryBuilder()
|
||||
->select('id', 'MAX(max_1, max_2) as j')
|
||||
->from('t', 'a')
|
||||
->leftJoin('a', 'products', 'p', 'p.id = a.id_product')
|
||||
->setForceIndexForJoin('p', 'PRIMARY');
|
||||
|
||||
$this->assertSame('SELECT id, MAX(max_1, max_2) as j FROM t a LEFT JOIN products p FORCE INDEX (PRIMARY) ON p.id = a.id_product', $queryBuilder->getSQL());
|
||||
}
|
||||
|
||||
public function testLimitDeleteThrowsMultitable01()
|
||||
{
|
||||
$qb = sqlQueryBuilder()->delete('orders', 'o')
|
||||
->setMaxResults(100);
|
||||
|
||||
$this->expectException(\Doctrine\DBAL\Exception::class);
|
||||
$qb->execute();
|
||||
}
|
||||
|
||||
public function testLimitDeleteThrowsMultitable02()
|
||||
{
|
||||
$qb = sqlQueryBuilder()->delete('orders')
|
||||
->join('orders', 'orders_history', 'oh', 'oh.id_order = orders.id')
|
||||
->setMaxResults(100);
|
||||
|
||||
$this->expectException(\Doctrine\DBAL\Exception::class);
|
||||
$qb->execute();
|
||||
}
|
||||
|
||||
public function testLimitDelete()
|
||||
{
|
||||
$qb = sqlQueryBuilder()->delete('orders')
|
||||
->setMaxResults(1000);
|
||||
|
||||
$qb->execute(); // Should not throw
|
||||
$this->assertSame('DELETE FROM orders LIMIT 1000', $qb->getSQL());
|
||||
|
||||
$qb = sqlQueryBuilder()->delete('orders_history')
|
||||
->andWhere(Operator::exists(
|
||||
sqlQueryBuilder()->select('*')
|
||||
->from('orders', 'o')
|
||||
->where('o.id = id_order')
|
||||
->andWhere('o.date_updated < (NOW() - INTERVAL 5 YEAR)')
|
||||
))
|
||||
->andWhere('custom_data IS NOT NULL')
|
||||
->andWhere("JSON_EXISTS(custom_data, '$.email_type')")
|
||||
->andWhere("JSON_EXTRACT(custom_data, '$.email_type') <> 'ORDER_CREATE'")
|
||||
->orderBy('date', 'DESC')
|
||||
->addOrderBy('id', 'ASC')
|
||||
->setMaxResults(1000);
|
||||
|
||||
$qb->execute(); // Should not throw
|
||||
$this->assertSame(
|
||||
"DELETE FROM orders_history WHERE (EXISTS (SELECT * FROM orders o WHERE (o.id = id_order) AND (o.date_updated < (NOW() - INTERVAL 5 YEAR)))) AND (custom_data IS NOT NULL) AND (JSON_EXISTS(custom_data, '$.email_type')) AND (JSON_EXTRACT(custom_data, '$.email_type') <> 'ORDER_CREATE') ORDER BY date DESC, id ASC LIMIT 1000",
|
||||
$qb->getSQL(),
|
||||
);
|
||||
|
||||
// Test normal delete still functional
|
||||
$qb = sqlQueryBuilder()->delete()
|
||||
->from('orders_history', 'oh')
|
||||
->andWhere(Operator::exists(
|
||||
sqlQueryBuilder()->select('*')
|
||||
->from('orders', 'o')
|
||||
->where('o.id = id_order')
|
||||
->andWhere('o.date_updated < (NOW() - INTERVAL 5 YEAR)')
|
||||
))
|
||||
->andWhere('custom_data IS NOT NULL')
|
||||
->andWhere("JSON_EXISTS(custom_data, '$.email_type')")
|
||||
->andWhere("JSON_EXTRACT(custom_data, '$.email_type') <> 'ORDER_CREATE'");
|
||||
|
||||
$qb->execute(); // Should not throw
|
||||
$this->assertSame(
|
||||
"DELETE oh FROM orders_history oh WHERE (EXISTS (SELECT * FROM orders o WHERE (o.id = id_order) AND (o.date_updated < (NOW() - INTERVAL 5 YEAR)))) AND (custom_data IS NOT NULL) AND (JSON_EXISTS(custom_data, '$.email_type')) AND (JSON_EXTRACT(custom_data, '$.email_type') <> 'ORDER_CREATE')",
|
||||
$qb->getSQL(),
|
||||
);
|
||||
|
||||
$qb = sqlQueryBuilder()->delete('orders_history', 'oh')
|
||||
->andWhere(Operator::exists(
|
||||
sqlQueryBuilder()->select('*')
|
||||
->from('orders', 'o')
|
||||
->where('o.id = id_order')
|
||||
->andWhere('o.date_updated < (NOW() - INTERVAL 5 YEAR)')
|
||||
))
|
||||
->andWhere('custom_data IS NOT NULL')
|
||||
->andWhere("JSON_EXISTS(custom_data, '$.email_type')")
|
||||
->andWhere("JSON_EXTRACT(custom_data, '$.email_type') <> 'ORDER_CREATE'");
|
||||
|
||||
$qb->execute(); // Should not throw
|
||||
$this->assertSame(
|
||||
"DELETE oh FROM orders_history oh WHERE (EXISTS (SELECT * FROM orders o WHERE (o.id = id_order) AND (o.date_updated < (NOW() - INTERVAL 5 YEAR)))) AND (custom_data IS NOT NULL) AND (JSON_EXISTS(custom_data, '$.email_type')) AND (JSON_EXTRACT(custom_data, '$.email_type') <> 'ORDER_CREATE')",
|
||||
$qb->getSQL(),
|
||||
);
|
||||
}
|
||||
|
||||
public function testLimitUpdate()
|
||||
{
|
||||
$qb = sqlQueryBuilder()
|
||||
->update('orders_history', 'oh')
|
||||
->set('oh.comment', 'NULL')
|
||||
->orderBy('oh.date')
|
||||
->setMaxResults(10000);
|
||||
|
||||
$qb->execute();
|
||||
$this->assertSame('UPDATE orders_history oh SET oh.comment = NULL ORDER BY oh.date ASC LIMIT 10000', $qb->getSQL());
|
||||
|
||||
$qb = sqlQueryBuilder()
|
||||
->update('orders_history', 'oh')
|
||||
->set('oh.comment', 'NULL');
|
||||
|
||||
$qb->execute();
|
||||
$this->assertSame('UPDATE orders_history oh SET oh.comment = NULL', $qb->getSQL());
|
||||
|
||||
$qb = sqlQueryBuilder()
|
||||
->update('orders_history', 'oh')
|
||||
->join('oh', 'orders', 'o', 'oh.id_order = o.id')
|
||||
->set('oh.comment', 'NULL');
|
||||
|
||||
$qb->execute();
|
||||
$this->assertSame('UPDATE orders_history oh INNER JOIN orders o ON oh.id_order = o.id SET oh.comment = NULL', $qb->getSQL());
|
||||
|
||||
$qb = sqlQueryBuilder()
|
||||
->update('orders_history', 'oh')
|
||||
->join('oh', 'orders', 'o', 'oh.id_order = o.id')
|
||||
->set('oh.comment', 'NULL')
|
||||
->orderBy('oh.date', 'DESC')
|
||||
->addOrderBy('o.date_updated', 'ASC')
|
||||
->setMaxResults(10000);
|
||||
|
||||
// https://mariadb.com/kb/en/update/#description
|
||||
// "Until MariaDB 10.3.2, for the multiple-table syntax, UPDATE updates rows in each table named in table_references that satisfy the conditions.
|
||||
// In this case, ORDER BY and LIMIT cannot be used. This restriction was lifted in MariaDB 10.3.2 and both clauses can be used with multiple-table updates."
|
||||
$qb->execute(); // Should not throw
|
||||
$this->assertSame('UPDATE orders_history oh INNER JOIN orders o ON oh.id_order = o.id SET oh.comment = NULL ORDER BY oh.date DESC, o.date_updated ASC LIMIT 10000', $qb->getSQL());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user