33 * Copyright © Magento, Inc. All rights reserved.
44 * See COPYING.txt for license details.
55 */
6+
67namespace Magento \Framework \DB \DataConverter ;
78
9+ use Magento \Framework \App \ResourceConnection ;
10+ use Magento \Framework \DB \Adapter \AdapterInterface ;
811use Magento \Framework \DB \Adapter \Pdo \Mysql ;
12+ use Magento \Framework \DB \Ddl \Table ;
13+ use Magento \Framework \DB \FieldDataConversionException ;
914use Magento \Framework \DB \FieldDataConverter ;
1015use Magento \Framework \DB \Select ;
1116use Magento \Framework \DB \Select \QueryModifierInterface ;
1419use Magento \Framework \DB \Query \Generator ;
1520use Magento \Framework \DB \Query \BatchIterator ;
1621use Magento \Framework \ObjectManagerInterface ;
22+ use PHPUnit \Framework \MockObject \MockObject ;
23+ use PHPUnit \Framework \TestCase ;
1724
18- class DataConverterTest extends \ PHPUnit \ Framework \ TestCase
25+ class DataConverterTest extends TestCase
1926{
2027 /**
21- * @var InQueryModifier|\PHPUnit\Framework\MockObject\ MockObject
28+ * @var InQueryModifier|MockObject
2229 */
2330 private $ queryModifierMock ;
2431
@@ -28,22 +35,22 @@ class DataConverterTest extends \PHPUnit\Framework\TestCase
2835 private $ dataConverter ;
2936
3037 /**
31- * @var BatchIterator|\PHPUnit\Framework\MockObject\ MockObject
38+ * @var BatchIterator|MockObject
3239 */
3340 private $ iteratorMock ;
3441
3542 /**
36- * @var Generator|\PHPUnit\Framework\MockObject\ MockObject
43+ * @var Generator|MockObject
3744 */
3845 private $ queryGeneratorMock ;
3946
4047 /**
41- * @var Select|\PHPUnit\Framework\MockObject\ MockObject
48+ * @var Select|MockObject
4249 */
4350 private $ selectByRangeMock ;
4451
4552 /**
46- * @var Mysql|\PHPUnit\Framework\MockObject\ MockObject
53+ * @var Mysql|MockObject
4754 */
4855 private $ adapterMock ;
4956
@@ -114,18 +121,22 @@ function () use (&$iterationComplete) {
114121
115122 $ this ->adapterMock = $ this ->getMockBuilder (Mysql::class)
116123 ->disableOriginalConstructor ()
117- ->setMethods (['fetchPairs ' , 'quoteInto ' , 'update ' ])
124+ ->setMethods (['fetchPairs ' , 'fetchAll ' , ' quoteInto ' , 'update ' , ' prepareSqlCondition ' ])
118125 ->getMock ();
119126
120127 $ this ->adapterMock ->expects ($ this ->any ())
121128 ->method ('quoteInto ' )
122129 ->willReturn ('field=value ' );
123130
131+ $ batchIteratorFactory = $ this ->createMock (\Magento \Framework \DB \Query \BatchRangeIteratorFactory::class);
132+ $ batchIteratorFactory ->method ('create ' )->willReturn ($ this ->iteratorMock );
133+
124134 $ this ->fieldDataConverter = $ this ->objectManager ->create (
125135 FieldDataConverter::class,
126136 [
127137 'queryGenerator ' => $ this ->queryGeneratorMock ,
128- 'dataConverter ' => $ this ->dataConverter
138+ 'dataConverter ' => $ this ->dataConverter ,
139+ 'batchIteratorFactory ' => $ batchIteratorFactory ,
129140 ]
130141 );
131142 }
@@ -136,7 +147,7 @@ function () use (&$iterationComplete) {
136147 */
137148 public function testDataConvertErrorReporting ()
138149 {
139- $ this ->expectException (\ Magento \ Framework \ DB \ FieldDataConversionException::class);
150+ $ this ->expectException (FieldDataConversionException::class);
140151 $ this ->expectExceptionMessage ('Error converting field `value` in table `table` where `id`=2 using ' );
141152
142153 $ rows = [
@@ -179,4 +190,158 @@ public function testAlreadyConvertedDataSkipped()
179190
180191 $ this ->fieldDataConverter ->convert ($ this ->adapterMock , 'table ' , 'id ' , 'value ' , $ this ->queryModifierMock );
181192 }
193+
194+ public function testAlreadyConvertedDataSkippedWithCompositeIdentifier (): void
195+ {
196+ $ rows = [
197+ [
198+ 'key_one ' => 1 ,
199+ 'key_two ' => 1 ,
200+ 'value ' => '[] ' ,
201+ ],
202+ [
203+ 'key_one ' => 1 ,
204+ 'key_two ' => 2 ,
205+ 'value ' => '{} ' ,
206+ ],
207+ [
208+ 'key_one ' => 3 ,
209+ 'key_two ' => 3 ,
210+ 'value ' => 'N; ' ,
211+ ],
212+ [
213+ 'key_one ' => 4 ,
214+ 'key_two ' => 1 ,
215+ 'value ' => '{"valid": "json value"} ' ,
216+ ]
217+ ];
218+
219+ $ this ->adapterMock ->expects ($ this ->any ())
220+ ->method ('prepareSqlCondition ' )
221+ ->willReturnCallback (
222+ function ($ column , $ value ) {
223+ return "$ column = $ value " ;
224+ }
225+ );
226+
227+ $ this ->adapterMock ->expects ($ this ->any ())
228+ ->method ('fetchAll ' )
229+ ->with ($ this ->selectByRangeMock )
230+ ->willReturn ($ rows );
231+
232+ $ this ->adapterMock ->expects ($ this ->once ())
233+ ->method ('update ' )
234+ ->with ('table ' , ['value ' => 'null ' ], 'key_one = 3 AND key_two = 3 ' );
235+
236+ $ this ->fieldDataConverter ->convert ($ this ->adapterMock , 'table ' , 'id1,id2 ' , 'value ' , $ this ->queryModifierMock );
237+ }
238+
239+ /**
240+ * @magentoDbIsolation disabled
241+ * @magentoDataFixture createFixtureTable
242+ */
243+ public function testTableWithCompositeIdentifier (): void
244+ {
245+ $ resource = Bootstrap::getObjectManager ()->get (ResourceConnection::class);
246+ $ tableName = 'test_fixture_table ' ;
247+ $ keyOneValues = range (1 , 9 );
248+ $ keyTwoValues = [3 , 6 , 9 ];
249+ $ records = [];
250+ foreach ($ keyOneValues as $ keyOneValue ) {
251+ foreach (array_slice ($ keyTwoValues , 0 , rand (1 , 3 )) as $ keyTwoValue ) {
252+ $ records [] = [
253+ 'key_one ' => $ keyOneValue ,
254+ 'key_two ' => $ keyTwoValue ,
255+ // phpcs:ignore
256+ 'value ' => serialize (['key_one ' => $ keyOneValue , 'key_two ' => $ keyTwoValue ]),
257+ ];
258+ }
259+ }
260+ // phpcs:ignore
261+ $ repeatedVal = serialize ([]);
262+ $ records [] = [
263+ 'key_one ' => 10 ,
264+ 'key_two ' => 3 ,
265+ 'value ' => $ repeatedVal ,
266+ ];
267+ $ records [] = [
268+ 'key_one ' => 10 ,
269+ 'key_two ' => 6 ,
270+ 'value ' => $ repeatedVal ,
271+ ];
272+ $ records [] = [
273+ 'key_one ' => 11 ,
274+ 'key_two ' => 6 ,
275+ 'value ' => $ repeatedVal ,
276+ ];
277+
278+ $ resource ->getConnection ()->insertMultiple ($ tableName , $ records );
279+
280+ $ expected = [];
281+
282+ foreach ($ records as $ record ) {
283+ $ record ['value ' ] = $ this ->dataConverter ->convert ($ record ['value ' ]);
284+ $ expected [] = $ record ;
285+ }
286+
287+ $ batchSize = 5 ;
288+ $ fieldDataConverter = $ this ->objectManager ->create (
289+ FieldDataConverter::class,
290+ [
291+ 'dataConverter ' => $ this ->dataConverter ,
292+ 'envBatchSize ' => $ batchSize
293+ ]
294+ );
295+ $ fieldDataConverter ->convert ($ resource ->getConnection (), $ tableName , 'key_one,key_two ' , 'value ' );
296+ $ actual = $ resource ->getConnection ()->fetchAll (
297+ $ resource ->getConnection ()->select ()->from ($ tableName )
298+ );
299+ $ this ->assertEquals ($ expected , $ actual , json_encode ($ records ));
300+ }
301+
302+ public static function createFixtureTable (): void
303+ {
304+ $ resource = Bootstrap::getObjectManager ()->get (ResourceConnection::class);
305+ $ tableName = 'test_fixture_table ' ;
306+ $ table = $ resource ->getConnection ()
307+ ->newTable (
308+ $ tableName
309+ )
310+ ->addColumn (
311+ 'key_one ' ,
312+ Table::TYPE_INTEGER ,
313+ null ,
314+ ['unsigned ' => true , 'nullable ' => false ]
315+ )
316+ ->addColumn (
317+ 'key_two ' ,
318+ Table::TYPE_INTEGER ,
319+ null ,
320+ ['unsigned ' => true , 'nullable ' => false ]
321+ )
322+ ->addColumn (
323+ 'value ' ,
324+ Table::TYPE_TEXT ,
325+ null ,
326+ ['nullable ' => true ]
327+ )
328+ ->addIndex (
329+ $ tableName . '_index_key_one_key_two ' ,
330+ [
331+ 'key_one ' ,
332+ 'key_two ' ,
333+ ],
334+ [
335+ 'type ' => AdapterInterface::INDEX_TYPE_PRIMARY
336+ ]
337+ );
338+ $ resource ->getConnection ()->createTable ($ table );
339+ }
340+
341+ public static function createFixtureTableRollback (): void
342+ {
343+ $ resource = Bootstrap::getObjectManager ()->get (ResourceConnection::class);
344+ $ tableName = 'test_fixture_table ' ;
345+ $ resource ->getConnection ()->dropTable ($ tableName );
346+ }
182347}
0 commit comments